Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git
A .classpath
1
<?xml version="1.0" encoding="UTF-8"?>
2
<classpath>
3
	<classpathentry kind="src" path="src/main/java">
4
		<attributes>
5
			<attribute name="FROM_GRADLE_MODEL" value="true"/>
6
		</attributes>
7
	</classpathentry>
8
	<classpathentry kind="src" path="src/main/resources">
9
		<attributes>
10
			<attribute name="FROM_GRADLE_MODEL" value="true"/>
11
		</attributes>
12
	</classpathentry>
13
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
14
		<accessrules>
15
			<accessrule kind="accessible" pattern="javafx/**"/>
16
		</accessrules>
17
	</classpathentry>
18
	<classpathentry exported="true" kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
19
	<classpathentry kind="output" path="bin"/>
20
</classpath>
121
A .gitattributes
1
# always use LF line endings
2
3
# ALL FILES:
4
#   Normalize line endings to LF on checkin and
5
#   prevent conversion to CRLF when the file is checked out.
6
7
* text eol=lf
8
9
10
# BINARY FILES:
11
#   Disable line ending normalize on checkin.
12
13
*.gif binary
14
*.jar binary
15
*.png binary
16
*.zip binary
117
A .gitignore
1
/bin/
2
/build/
3
/.gradle/
4
/gradle/
5
/.nb-gradle
6
/private
7
.nb-gradle-properties
8
scrivenvar.pro
19
A .project
1
<?xml version="1.0" encoding="UTF-8"?>
2
<projectDescription>
3
	<name>Markdown Writer FX</name>
4
	<comment></comment>
5
	<projects>
6
	</projects>
7
	<buildSpec>
8
		<buildCommand>
9
			<name>org.eclipse.jdt.core.javabuilder</name>
10
			<arguments>
11
			</arguments>
12
		</buildCommand>
13
		<buildCommand>
14
			<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
15
			<arguments>
16
			</arguments>
17
		</buildCommand>
18
	</buildSpec>
19
	<natures>
20
		<nature>org.eclipse.jdt.core.javanature</nature>
21
		<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
22
	</natures>
23
	<filteredResources>
24
		<filter>
25
			<id>1438449113801</id>
26
			<name></name>
27
			<type>26</type>
28
			<matcher>
29
				<id>org.eclipse.ui.ide.multiFilter</id>
30
				<arguments>1.0-projectRelativePath-matches-false-false-build</arguments>
31
			</matcher>
32
		</filter>
33
		<filter>
34
			<id>1438449113801</id>
35
			<name></name>
36
			<type>26</type>
37
			<matcher>
38
				<id>org.eclipse.ui.ide.multiFilter</id>
39
				<arguments>1.0-projectRelativePath-matches-false-false-.gradle</arguments>
40
			</matcher>
41
		</filter>
42
	</filteredResources>
43
</projectDescription>
144
A .settings/org.eclipse.buildship.core.prefs
1
GRADLE_BUILD_COMMANDS=org.eclipse.jdt.core.javabuilder
2
GRADLE_NATURES=org.eclipse.jdt.core.javanature
3
build.commands=org.eclipse.jdt.core.javabuilder
4
connection.arguments=
5
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
6
connection.gradle.user.home=null
7
connection.java.home=null
8
connection.jvm.arguments=
9
connection.project.dir=
10
derived.resources=.gradle,build
11
eclipse.preferences.version=1
12
natures=org.eclipse.jdt.core.javanature
13
project.path=\:
114
A .settings/org.eclipse.core.runtime.prefs
1
eclipse.preferences.version=1
2
line.separator=\n
13
A .settings/org.eclipse.jdt.core.prefs
1
eclipse.preferences.version=1
2
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
3
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
4
org.eclipse.jdt.core.compiler.compliance=1.8
5
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
6
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
7
org.eclipse.jdt.core.compiler.source=1.8
18
A .settings/org.eclipse.jdt.ui.prefs
1
eclipse.preferences.version=1
2
editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
3
org.eclipse.jdt.ui.ignorelowercasenames=true
4
org.eclipse.jdt.ui.importorder=java;javafx;javax;org;com;
5
org.eclipse.jdt.ui.ondemandthreshold=99
6
org.eclipse.jdt.ui.staticondemandthreshold=99
7
sp_cleanup.add_default_serial_version_id=true
8
sp_cleanup.add_generated_serial_version_id=false
9
sp_cleanup.add_missing_annotations=true
10
sp_cleanup.add_missing_deprecated_annotations=true
11
sp_cleanup.add_missing_methods=false
12
sp_cleanup.add_missing_nls_tags=false
13
sp_cleanup.add_missing_override_annotations=true
14
sp_cleanup.add_missing_override_annotations_interface_methods=true
15
sp_cleanup.add_serial_version_id=false
16
sp_cleanup.always_use_blocks=true
17
sp_cleanup.always_use_parentheses_in_expressions=false
18
sp_cleanup.always_use_this_for_non_static_field_access=false
19
sp_cleanup.always_use_this_for_non_static_method_access=false
20
sp_cleanup.convert_functional_interfaces=false
21
sp_cleanup.convert_to_enhanced_for_loop=false
22
sp_cleanup.correct_indentation=false
23
sp_cleanup.format_source_code=false
24
sp_cleanup.format_source_code_changes_only=false
25
sp_cleanup.insert_inferred_type_arguments=false
26
sp_cleanup.make_local_variable_final=false
27
sp_cleanup.make_parameters_final=false
28
sp_cleanup.make_private_fields_final=true
29
sp_cleanup.make_type_abstract_if_missing_method=false
30
sp_cleanup.make_variable_declarations_final=true
31
sp_cleanup.never_use_blocks=false
32
sp_cleanup.never_use_parentheses_in_expressions=true
33
sp_cleanup.on_save_use_additional_actions=true
34
sp_cleanup.organize_imports=false
35
sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
36
sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
37
sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
38
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
39
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
40
sp_cleanup.remove_private_constructors=true
41
sp_cleanup.remove_redundant_type_arguments=true
42
sp_cleanup.remove_trailing_whitespaces=true
43
sp_cleanup.remove_trailing_whitespaces_all=true
44
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
45
sp_cleanup.remove_unnecessary_casts=true
46
sp_cleanup.remove_unnecessary_nls_tags=false
47
sp_cleanup.remove_unused_imports=true
48
sp_cleanup.remove_unused_local_variables=false
49
sp_cleanup.remove_unused_private_fields=true
50
sp_cleanup.remove_unused_private_members=false
51
sp_cleanup.remove_unused_private_methods=true
52
sp_cleanup.remove_unused_private_types=true
53
sp_cleanup.sort_members=false
54
sp_cleanup.sort_members_all=false
55
sp_cleanup.use_anonymous_class_creation=false
56
sp_cleanup.use_blocks=false
57
sp_cleanup.use_blocks_only_for_return_and_throw=false
58
sp_cleanup.use_lambda=true
59
sp_cleanup.use_parentheses_in_expressions=false
60
sp_cleanup.use_this_for_non_static_field_access=false
61
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
62
sp_cleanup.use_this_for_non_static_method_access=false
63
sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
64
sp_cleanup.use_type_arguments=false
165
A .travis.yml
1
language: java
2
3
jdk:
4
- oraclejdk8
5
6
# enable Java 8u45+, see https://github.com/travis-ci/travis-ci/issues/4042
7
addons:
8
  apt:
9
    packages:
10
      - oracle-java8-installer
11
os:
12
  - linux
13
14
# run in container
15
sudo: false
116
A CHANGES.md
1
# Change Log
2
3
## 0.7
4
5
- Load YAML variables from files
6
- Added cursor to the preview pane
7
- Reconfigured constants to use settings
8
- Organized MainWindow code by similar method calls
9
- Added single entry point for refreshing file editor tab
10
11
## 0.6
12
13
- Revised synchronized scrolling with preview panel
14
- Added universal character encoding detection
15
- Removed options panel
16
- Decoupled Editor Tab and Preview Pane
17
18
## 0.5
19
20
- Added document processors for Markdown and Variables
21
- Simplified code base
22
- Added `Ctrl+Space` hot key for quick variable injection
23
- Replaced commonmark-java with flexmark
24
- Insert `CARETPOSITION` into document for preview pane scroll position reference
25
26
## 0.4
27
28
- Changed name to Scrivenvar
29
- Added hot-keys for variable mode and autocomplete
30
- Replaced pegdown with commonmark-java
31
- Started document processors to provide XSLT and variable dereferencing
32
33
## 0.3
34
35
- Changed name to Scrivendor
36
- Changed logo to match
37
- Started to implement service-oriented architecture
38
39
## 0.2
40
41
- RichTextFX (and dependencies) updated to version 0.6.10 (fixes bugs)
42
- pegdown Markdown parser updated to version 1.6
43
- Added five new pegdown 1.6 extension flags to Markdown Options tab
44
- Minor improvements
45
46
## 0.1
147
48
- Initial release
A CREDITS.md
1
Credits
2
===
3
4
  * Dave Jarvis: [Scrivenvar](https://github.com/DaveJarvis/scrivenvar/)
5
  * Karl Tauber: [Markdown Writer FX](https://github.com/JFormDesigner/markdown-writer-fx)
6
  * Tomas Mikula: [RichTextFX](https://github.com/TomasMikula/RichTextFX), [ReactFX](https://github.com/TomasMikula/ReactFX), [WellBehavedFX](https://github.com/TomasMikula/WellBehavedFX), [Flowless](https://github.com/TomasMikula/Flowless), and [UndoFX](https://github.com/TomasMikula/UndoFX)
7
  * Mikael Grev: [MigLayout](http://www.miglayout.com/)
8
  * Tom Eugelink: [MigPane](https://github.com/mikaelgrev/miglayout/blob/master/javafx/src/main/java/org/tbee/javafx/scene/layout/fxml/MigPane.java)
9
  * Vladimir Schneider: [flexmark](https://website.com)
10
  * Jens Deters: [FontAwesomeFX](https://bitbucket.org/Jerady/fontawesomefx)
11
  * Shy Shalom, Kohei Taketa: [juniversalchardet](https://github.com/takscape/juniversalchardet)
112
A LICENSE
1
Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
2
All rights reserved.
3
4
Redistribution and use in source and binary forms, with or without
5
modification, are permitted provided that the following conditions are met:
6
7
* Redistributions of source code must retain the above copyright
8
  notice, this list of conditions and the following disclaimer.
9
10
* Redistributions in binary form must reproduce the above copyright
11
  notice, this list of conditions and the following disclaimer in the
12
  documentation and/or other materials provided with the distribution.
13
14
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
125
A README.md
1
![Logo](images/logo64.png)
2
3
Scrivenvar
4
===
5
6
Word processing with variables.
7
8
![Screenshot](images/screenshot.png)
9
10
Features
11
---
12
13
* User-defined variables
14
* Recursive variable definitions
15
* Real-time document preview with variable substitution
16
* Platform independent (Windows, Linux, MacOS)
17
* Auto-insert variable names pressing `Control+Space`
18
19
Future Features
20
---
21
* Spell check
22
* Search and replace, with or without variables
23
* XML and XSL processing
24
* R integration using [Rserve](https://rforge.net/Rserve/)
25
* Re-organize variable names
26
27
Requirements
28
---
29
30
Download and install [Java 8u40](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html).
31
32
Installation
33
---
34
1. [Download](https://github.com/DaveJarvis/scrivenvar/releases) the latest zip archive and extract it to any folder.
35
1. Double-click `scrivenvar.jar` to start the application.
36
37
License
38
---
39
40
This software is licensed under the [BSD 2-Clause License](LICENSE).
141
A Scrivenvar.jfdproj
1
<?xml version="1.0" encoding="UTF-8"?>
2
<project>
3
  <entry key="classpath1" value="bin" />
4
  <entry key="classpath2" value="lib/fontawesomefx-8.9.jar" />
5
  <entry key="sourcefolders1" value="src/main/java" />
6
  <entry key="sourcefolders2" value="src/main/resources" />
7
  <node name="designer">
8
    <entry key="_i18nJavaSettingsEnabled" value="true" />
9
    <entry key="_i18nSettingsEnabled" value="true" />
10
    <entry key="i18n.externalizeexcludes1" value="com.scrivenvar.controls.EscapeTextField#escapeCharacters" />
11
    <entry key="i18n.externalizeexcludes2" value="com.scrivenvar.controls.WebHyperlink#uri" />
12
    <entry key="i18n.javagetstringformat" value="Messages.get(${key})" />
13
  </node>
14
  <node name="javacodegenerator">
15
    <entry key="_codeStyleSettingsEnabled" value="true" />
16
    <entry key="_generalSettingsEnabled" value="true" />
17
    <entry key="explicitimports" value="true" />
18
    <entry key="lineseparator" value="\n" />
19
  </node>
20
</project>
121
A USAGE.md
1
# Introduction
2
3
This document describes how to write documentation (technical or otherwise) using a master copy for generating a variety of output formats, such as: HTML pages, PDFs, and EPUBs. What's more, the document provides an overview of how to use variables and--for the unintimidated--leverage the power of R, a programming language.
4
5
# Software Requirements
6
7
Install Java, ConTeXt, Pandoc, R, and Lib V8. Then install the R packages knitr, yaml, and devtools, and pluralize by running the following commands:
8
9
    sudo su -
10
    apt-get install default-jre
11
    apt-get install context
12
    apt-get install pandoc
13
    apt-get install r
14
    apt-get install libv8-dev
15
    r
16
    url <- "http://cran.us.r-project.org"
17
    install.packages('knitr', repos=url)
18
    install.packages('yaml', repos=url)
19
    install.packages('devtools', repos=url)
20
    devtools::install_github("hrbrmstr/pluralize")
21
22
To exit R, press `Ctrl+d` or type `q()` followed by pressing `Enter`.
23
24
The required software packages are installed.
25
26
# Markdown
27
28
|Table|Table|
29
|---|---|
30
|Data|Data|
131
A build.gradle
1
version = '0.5'
2
3
apply plugin: 'java'
4
apply plugin: 'java-library-distribution'
5
apply plugin: 'application'
6
7
sourceCompatibility = 1.8
8
9
mainClassName = 'com.scrivenvar.Main'
10
11
repositories {
12
	jcenter()
13
}
14
15
compileJava {
16
  options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
17
}
18
19
dependencies {
20
  compile group: 'org.fxmisc.richtext', name: 'richtextfx', version: '0.7-M2'
21
	compile group: 'com.miglayout', name: 'miglayout-javafx', version: '5.0'
22
	compile group: 'de.jensd', name: 'fontawesomefx-fontawesome', version: '4.5.0'
23
  compile group: 'org.ahocorasick', name: 'ahocorasick', version: '0.3.0'
24
  compile group: 'com.vladsch.flexmark', name: 'flexmark', version: '0.6.1'
25
  compile group: 'com.vladsch.flexmark', name: 'flexmark-ext-gfm-tables', version: '0.6.1'
26
  compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.8.4'
27
  compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.8.4'
28
  compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.8.4'
29
  compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.8.4'
30
  compile group: 'org.yaml', name: 'snakeyaml', version: '1.17'
31
  compile group: 'com.googlecode.juniversalchardet', name: 'juniversalchardet', version: '1.0.3'
32
  compile group: 'commons-configuration', name: 'commons-configuration', version: '1.10'
33
}
34
35
jar {
36
	baseName = 'scrivenvar'
37
  
38
  from {
39
    (configurations.runtime).collect {
40
      it.isDirectory() ? it : zipTree(it)
41
    }
42
  }
43
    
44
	manifest {
45
		attributes 'Main-Class': mainClassName,
46
					'Class-Path': configurations.compile.collect { 'lib/' + it.getName() }.join(' ')
47
	}
48
}
49
50
distributions {
51
	main {
52
		baseName = 'scrivenvar'
53
		contents {
54
			from { ['LICENSE', 'README.md'] }
55
			into( 'images' ) {
56
				from { 'images' }
57
			}
58
		}
59
	}
60
}
161
A gradle.properties
11
A images/logo64.png
Binary file
A images/screenshot.png
Binary file
A settings.gradle
11
A src/main/java/com/scrivenvar/AbstractPane.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar;
29
30
import com.scrivenvar.Services;
31
import com.scrivenvar.service.Options;
32
import java.util.prefs.Preferences;
33
import org.tbee.javafx.scene.layout.fxml.MigPane;
34
35
/**
36
 * Provides options to all subclasses.
37
 *
38
 * @author White Magic Software, Ltd.
39
 */
40
public abstract class AbstractPane extends MigPane {
41
42
  private final Options options = Services.load( Options.class );
43
44
  protected Options getOptions() {
45
    return this.options;
46
  }
47
  
48
  protected Preferences getState() {
49
    return getOptions().getState();
50
  }
51
}
152
A src/main/java/com/scrivenvar/Constants.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar;
29
30
import com.scrivenvar.service.Settings;
31
32
/**
33
 * @author White Magic Software, Ltd.
34
 */
35
public class Constants {
36
37
  private static final Settings SETTINGS = Services.load( Settings.class );
38
39
  /**
40
   * Prevent instantiation.
41
   */
42
  private Constants() {
43
  }
44
45
  private static String get( final String key ) {
46
    return SETTINGS.getSetting( key, "" );
47
  }
48
49
  // Bootstrapping...
50
  public static final String SETTINGS_NAME = "/com/scrivenvar/settings.properties";
51
52
  public static final String APP_BUNDLE_NAME = get( "application.messages" );
53
54
  public static final String STYLESHEET_SCENE = get( "file.stylesheet.scene" );
55
  public static final String STYLESHEET_MARKDOWN = get( "file.stylesheet.markdown" );
56
  public static final String STYLESHEET_PREVIEW = get( "file.stylesheet.preview" );
57
58
  public static final String FILE_LOGO_16 = get( "file.logo.16" );
59
  public static final String FILE_LOGO_32 = get( "file.logo.32" );
60
  public static final String FILE_LOGO_128 = get( "file.logo.128" );
61
  public static final String FILE_LOGO_256 = get( "file.logo.256" );
62
  public static final String FILE_LOGO_512 = get( "file.logo.512" );
63
64
  public static final String CARET_POSITION_BASE = get( "caret.token.base" );
65
  public static final String CARET_POSITION_MD = get( "caret.token.markdown" );
66
  public static final String CARET_POSITION_XML = get( "caret.token.xml" );
67
  public static final String CARET_POSITION_HTML = get( "caret.token.html" );
68
69
  public static final String PREFS_ROOT = get( "preferences.root" );
70
  public static final String PREFS_ROOT_STATE = get( "preferences.root.state" );
71
  public static final String PREFS_ROOT_OPTIONS = get( "preferences.root.options" );
72
}
173
A src/main/java/com/scrivenvar/FileEditorTab.java
1
/*
2
 * Copyright 2016 Karl Tauber and White Magic Software, Ltd.
3
 *
4
 * Redistribution and use in source and binary forms, with or without
5
 * modification, are permitted provided that the following conditions are met:
6
 *
7
 *  o Redistributions of source code must retain the above copyright
8
 *    notice, this list of conditions and the following disclaimer.
9
 *
10
 *  o Redistributions in binary form must reproduce the above copyright
11
 *    notice, this list of conditions and the following disclaimer in the
12
 *    documentation and/or other materials provided with the distribution.
13
 *
14
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
 */
26
package com.scrivenvar;
27
28
import com.scrivenvar.editors.EditorPane;
29
import com.scrivenvar.editors.markdown.MarkdownEditorPane;
30
import com.scrivenvar.service.Options;
31
import com.scrivenvar.service.events.AlertMessage;
32
import com.scrivenvar.service.events.AlertService;
33
import java.nio.charset.Charset;
34
import java.nio.file.Files;
35
import java.nio.file.Path;
36
import static java.util.Locale.ENGLISH;
37
import java.util.function.Consumer;
38
import javafx.application.Platform;
39
import javafx.beans.binding.Bindings;
40
import javafx.beans.property.BooleanProperty;
41
import javafx.beans.property.ReadOnlyBooleanProperty;
42
import javafx.beans.property.ReadOnlyBooleanWrapper;
43
import javafx.beans.property.SimpleBooleanProperty;
44
import javafx.beans.value.ChangeListener;
45
import javafx.event.Event;
46
import javafx.scene.Node;
47
import javafx.scene.control.Tab;
48
import javafx.scene.control.Tooltip;
49
import javafx.scene.input.InputEvent;
50
import javafx.scene.text.Text;
51
import org.fxmisc.undo.UndoManager;
52
import org.fxmisc.wellbehaved.event.EventPattern;
53
import org.fxmisc.wellbehaved.event.InputMap;
54
import org.mozilla.universalchardet.UniversalDetector;
55
56
/**
57
 * Editor for a single file.
58
 *
59
 * @author Karl Tauber and White Magic Software, Ltd.
60
 */
61
public final class FileEditorTab extends Tab {
62
63
  private final Options options = Services.load( Options.class );
64
  private final AlertService alertService = Services.load( AlertService.class );
65
66
  private EditorPane editorPane;
67
68
  /**
69
   * Character encoding used by the file (or default encoding if none found).
70
   */
71
  private Charset encoding;
72
73
  private final ReadOnlyBooleanWrapper modified = new ReadOnlyBooleanWrapper();
74
  private final BooleanProperty canUndo = new SimpleBooleanProperty();
75
  private final BooleanProperty canRedo = new SimpleBooleanProperty();
76
  private Path path;
77
78
  FileEditorTab( final Path path ) {
79
    setPath( path );
80
81
    this.modified.addListener( (observable, oldPath, newPath) -> updateTab() );
82
    updateTab();
83
84
    setOnSelectionChanged( e -> {
85
      if( isSelected() ) {
86
        Platform.runLater( () -> activated() );
87
      }
88
    } );
89
  }
90
91
  private void updateTab() {
92
    setText( getTabTitle() );
93
    setGraphic( getModifiedMark() );
94
    setTooltip( getTabTooltip() );
95
  }
96
97
  /**
98
   * Returns the base filename (without the directory names).
99
   *
100
   * @return The untitled text if the path hasn't been set.
101
   */
102
  private String getTabTitle() {
103
    final Path filePath = getPath();
104
105
    return (filePath == null)
106
      ? Messages.get( "FileEditor.untitled" )
107
      : filePath.getFileName().toString();
108
  }
109
110
  /**
111
   * Returns the full filename represented by the path.
112
   *
113
   * @return The untitled text if the path hasn't been set.
114
   */
115
  private Tooltip getTabTooltip() {
116
    final Path filePath = getPath();
117
118
    return (filePath == null)
119
      ? null
120
      : new Tooltip( filePath.toString() );
121
  }
122
123
  /**
124
   * Returns a marker to indicate whether the file has been modified.
125
   *
126
   * @return "*" when the file has changed; otherwise null.
127
   */
128
  private Text getModifiedMark() {
129
    return isModified() ? new Text( "*" ) : null;
130
  }
131
132
  /**
133
   * Called when the user switches tab.
134
   */
135
  private void activated() {
136
    // Tab is closed or no longer active.
137
    if( getTabPane() == null || !isSelected() ) {
138
      return;
139
    }
140
141
    // Switch to the tab without loading if the contents are already in memory.
142
    if( getContent() != null ) {
143
      getEditorPane().requestFocus();
144
      return;
145
    }
146
147
    // Load the text and update the preview before the undo manager.
148
    load();
149
150
    // Track undo requests -- can only be called *after* load.
151
    initUndoManager();
152
    initLayout();
153
    initFocus();
154
  }
155
156
  private void initLayout() {
157
    setContent( getScrollPane() );
158
  }
159
160
  private Node getScrollPane() {
161
    return getEditorPane().getScrollPane();
162
  }
163
164
  private void initFocus() {
165
    getEditorPane().requestFocus();
166
  }
167
168
  private void initUndoManager() {
169
    final UndoManager undoManager = getUndoManager();
170
171
    // Clear undo history after first load.
172
    undoManager.forgetHistory();
173
174
    // Bind the editor undo manager to the properties.
175
    modified.bind( Bindings.not( undoManager.atMarkedPositionProperty() ) );
176
    canUndo.bind( undoManager.undoAvailableProperty() );
177
    canRedo.bind( undoManager.redoAvailableProperty() );
178
  }
179
180
  /**
181
   * Returns the index into the text where the caret blinks happily away.
182
   *
183
   * @return A number from 0 to the editor's document text length.
184
   */
185
  public int getCaretPosition() {
186
    return getEditorPane().getEditor().getCaretPosition();
187
  }
188
189
  /**
190
   * Returns true if the given path exactly matches this tab's path.
191
   *
192
   * @param check The path to compare against.
193
   *
194
   * @return true The paths are the same.
195
   */
196
  public boolean isPath( final Path check ) {
197
    final Path filePath = getPath();
198
199
    return filePath == null ? false : filePath.equals( check );
200
  }
201
202
  /**
203
   * Reads the entire file contents from the path associated with this tab.
204
   */
205
  private void load() {
206
    final Path filePath = getPath();
207
208
    if( filePath != null ) {
209
      try {
210
        getEditorPane().setText( asString( Files.readAllBytes( filePath ) ) );
211
      } catch( Exception ex ) {
212
        alert(
213
          "FileEditor.loadFailed.title", "FileEditor.loadFailed.message", ex
214
        );
215
      }
216
    }
217
  }
218
219
  /**
220
   * Saves the entire file contents from the path associated with this tab.
221
   *
222
   * @return true The file has been saved.
223
   */
224
  public boolean save() {
225
    try {
226
      Files.write( getPath(), asBytes( getEditorPane().getText() ) );
227
      getEditorPane().getUndoManager().mark();
228
      return true;
229
    } catch( Exception ex ) {
230
      return alert(
231
        "FileEditor.saveFailed.title", "FileEditor.saveFailed.message", ex
232
      );
233
    }
234
  }
235
236
  /**
237
   * Creates an alert dialog and waits for it to close.
238
   *
239
   * @param titleKey Resource bundle key for the alert dialog title.
240
   * @param messageKey Resource bundle key for the alert dialog message.
241
   * @param e The unexpected happening.
242
   *
243
   * @return false
244
   */
245
  private boolean alert(
246
    final String titleKey, final String messageKey, final Exception e ) {
247
    final AlertService service = getAlertService();
248
249
    final AlertMessage message = service.createAlertMessage(
250
      Messages.get( titleKey ),
251
      Messages.get( messageKey ),
252
      getPath(),
253
      e.getMessage()
254
    );
255
256
    service.createAlertError( message ).showAndWait();
257
    return false;
258
  }
259
260
  /**
261
   * Returns a best guess at the file encoding. If the encoding could not be
262
   * detected, this will return the default charset for the JVM.
263
   *
264
   * @param bytes The bytes to perform character encoding detection.
265
   *
266
   * @return The character encoding.
267
   */
268
  private Charset detectEncoding( final byte[] bytes ) {
269
    final UniversalDetector detector = new UniversalDetector( null );
270
    detector.handleData( bytes, 0, bytes.length );
271
    detector.dataEnd();
272
273
    final String charset = detector.getDetectedCharset();
274
    final Charset charEncoding = charset == null
275
      ? Charset.defaultCharset()
276
      : Charset.forName( charset.toUpperCase( ENGLISH ) );
277
278
    detector.reset();
279
280
    return charEncoding;
281
  }
282
283
  /**
284
   * Converts the given string to an array of bytes using the encoding that was
285
   * originally detected (if any) and associated with this file.
286
   *
287
   * @param text The text to convert into the original file encoding.
288
   *
289
   * @return A series of bytes ready for writing to a file.
290
   */
291
  private byte[] asBytes( final String text ) {
292
    return text.getBytes( getEncoding() );
293
  }
294
295
  /**
296
   * Converts the given bytes into a Java String. This will call setEncoding
297
   * with the encoding detected by the CharsetDetector.
298
   *
299
   * @param text The text of unknown character encoding.
300
   *
301
   * @return The text, in its auto-detected encoding, as a String.
302
   */
303
  private String asString( final byte[] text ) {
304
    setEncoding( detectEncoding( text ) );
305
    return new String( text, getEncoding() );
306
  }
307
308
  Path getPath() {
309
    return this.path;
310
  }
311
312
  void setPath( final Path path ) {
313
    this.path = path;
314
  }
315
316
  public boolean isModified() {
317
    return this.modified.get();
318
  }
319
320
  ReadOnlyBooleanProperty modifiedProperty() {
321
    return this.modified.getReadOnlyProperty();
322
  }
323
324
  BooleanProperty canUndoProperty() {
325
    return this.canUndo;
326
  }
327
328
  BooleanProperty canRedoProperty() {
329
    return this.canRedo;
330
  }
331
332
  private UndoManager getUndoManager() {
333
    return getEditorPane().getUndoManager();
334
  }
335
336
  /**
337
   * Forwards the request to the editor pane.
338
   *
339
   * @param <T> The type of event listener to add.
340
   * @param <U> The type of consumer to add.
341
   * @param event The event that should trigger updates to the listener.
342
   * @param consumer The listener to receive update events.
343
   */
344
  public <T extends Event, U extends T> void addEventListener(
345
    final EventPattern<? super T, ? extends U> event,
346
    final Consumer<? super U> consumer ) {
347
    getEditorPane().addEventListener( event, consumer );
348
  }
349
350
  /**
351
   * Forwards to the editor pane's listeners for keyboard events.
352
   *
353
   * @param map The new input map to replace the existing keyboard listener.
354
   */
355
  public void addEventListener( final InputMap<InputEvent> map ) {
356
    getEditorPane().addEventListener( map );
357
  }
358
359
  /**
360
   * Forwards to the editor pane's listeners for keyboard events.
361
   *
362
   * @param map The existing input map to remove from the keyboard listeners.
363
   */
364
  public void removeEventListener( final InputMap<InputEvent> map ) {
365
    getEditorPane().removeEventListener( map );
366
  }
367
368
  /**
369
   * Forwards to the editor pane's listeners for text change events.
370
   *
371
   * @param listener The listener to notify when the text changes.
372
   */
373
  public void addTextChangeListener( final ChangeListener<String> listener ) {
374
    getEditorPane().addTextChangeListener( listener );
375
  }
376
377
  /**
378
   * Forwards to the editor pane's listeners for caret paragraph change events.
379
   *
380
   * @param listener The listener to notify when the caret changes paragraphs.
381
   */
382
  public void addCaretParagraphListener( final ChangeListener<Integer> listener ) {
383
    getEditorPane().addCaretParagraphListener( listener );
384
  }
385
386
  /**
387
   * Forwards the request to the editor pane.
388
   *
389
   * @return The text to process.
390
   */
391
  public String getEditorText() {
392
    return getEditorPane().getText();
393
  }
394
395
  /**
396
   * Returns the editor pane, or creates one if it doesn't yet exist.
397
   *
398
   * @return The editor pane, never null.
399
   */
400
  protected EditorPane getEditorPane() {
401
    if( this.editorPane == null ) {
402
      this.editorPane = new MarkdownEditorPane();
403
    }
404
405
    return this.editorPane;
406
  }
407
408
  private AlertService getAlertService() {
409
    return this.alertService;
410
  }
411
412
  private Options getOptions() {
413
    return this.options;
414
  }
415
416
  private Charset getEncoding() {
417
    return this.encoding;
418
  }
419
420
  private void setEncoding( final Charset encoding ) {
421
    this.encoding = encoding;
422
  }
423
424
  /**
425
   * Returns the tab title, without any modified indicators.
426
   *
427
   * @return The tab title.
428
   */
429
  @Override
430
  public String toString() {
431
    return getTabTitle();
432
  }
433
}
1434
A src/main/java/com/scrivenvar/FileEditorTabPane.java
1
/*
2
 * Copyright 2016 Karl Tauber and White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar;
29
30
import com.scrivenvar.predicates.files.FileTypePredicate;
31
import com.scrivenvar.service.Options;
32
import com.scrivenvar.service.Settings;
33
import com.scrivenvar.service.events.AlertMessage;
34
import com.scrivenvar.service.events.AlertService;
35
import static com.scrivenvar.service.events.AlertService.NO;
36
import static com.scrivenvar.service.events.AlertService.YES;
37
import com.scrivenvar.util.Utils;
38
import java.io.File;
39
import java.nio.file.Path;
40
import java.util.ArrayList;
41
import java.util.List;
42
import java.util.function.Consumer;
43
import java.util.prefs.Preferences;
44
import java.util.stream.Collectors;
45
import javafx.beans.property.ReadOnlyBooleanProperty;
46
import javafx.beans.property.ReadOnlyBooleanWrapper;
47
import javafx.beans.property.ReadOnlyObjectProperty;
48
import javafx.beans.property.ReadOnlyObjectWrapper;
49
import javafx.beans.value.ChangeListener;
50
import javafx.beans.value.ObservableValue;
51
import javafx.collections.ListChangeListener;
52
import javafx.collections.ObservableList;
53
import javafx.event.Event;
54
import javafx.scene.Node;
55
import javafx.scene.control.Alert;
56
import javafx.scene.control.ButtonType;
57
import javafx.scene.control.Tab;
58
import javafx.scene.control.TabPane;
59
import javafx.scene.control.TabPane.TabClosingPolicy;
60
import javafx.scene.input.InputEvent;
61
import javafx.stage.FileChooser;
62
import javafx.stage.FileChooser.ExtensionFilter;
63
import javafx.stage.Window;
64
import org.fxmisc.richtext.StyledTextArea;
65
import org.fxmisc.wellbehaved.event.EventPattern;
66
import org.fxmisc.wellbehaved.event.InputMap;
67
import static com.scrivenvar.Messages.get;
68
69
/**
70
 * Tab pane for file editors.
71
 *
72
 * @author Karl Tauber and White Magic Software, Ltd.
73
 */
74
public final class FileEditorTabPane extends TabPane {
75
76
  private final static String FILTER_EXTENSIONS = "filter.file";
77
  private final static String FILTER_EXTENSION_TITLES = "Dialog.file.choose.filter";
78
79
  private final Options options = Services.load( Options.class );
80
  private final Settings settings = Services.load( Settings.class );
81
  private final AlertService alertService = Services.load( AlertService.class );
82
83
  private final ReadOnlyObjectWrapper<Path> openDefinition = new ReadOnlyObjectWrapper<>();
84
  private final ReadOnlyObjectWrapper<FileEditorTab> activeFileEditor = new ReadOnlyObjectWrapper<>();
85
  private final ReadOnlyBooleanWrapper anyFileEditorModified = new ReadOnlyBooleanWrapper();
86
87
  /**
88
   * Constructs a new file editor tab pane.
89
   */
90
  public FileEditorTabPane() {
91
    final ObservableList<Tab> tabs = getTabs();
92
93
    setFocusTraversable( false );
94
    setTabClosingPolicy( TabClosingPolicy.ALL_TABS );
95
96
    addTabSelectionListener(
97
      (ObservableValue<? extends Tab> tabPane,
98
        final Tab oldTab, final Tab newTab) -> {
99
100
        if( newTab != null ) {
101
          activeFileEditor.set( (FileEditorTab)newTab );
102
        }
103
      }
104
    );
105
106
    final ChangeListener<Boolean> modifiedListener = (observable, oldValue, newValue) -> {
107
      for( final Tab tab : tabs ) {
108
        if( ((FileEditorTab)tab).isModified() ) {
109
          this.anyFileEditorModified.set( true );
110
          break;
111
        }
112
      }
113
    };
114
115
    tabs.addListener(
116
      (ListChangeListener<Tab>)change -> {
117
        while( change.next() ) {
118
          if( change.wasAdded() ) {
119
            change.getAddedSubList().stream().forEach( (tab) -> {
120
              ((FileEditorTab)tab).modifiedProperty().addListener( modifiedListener );
121
            } );
122
          } else if( change.wasRemoved() ) {
123
            change.getRemoved().stream().forEach( (tab) -> {
124
              ((FileEditorTab)tab).modifiedProperty().removeListener( modifiedListener );
125
            } );
126
          }
127
        }
128
129
        // Changes in the tabs may also change anyFileEditorModified property
130
        // (e.g. closed modified file)
131
        modifiedListener.changed( null, null, null );
132
      }
133
    );
134
  }
135
136
  /**
137
   * Delegates to the active file editor.
138
   *
139
   * @param <T> Event type.
140
   * @param <U> Consumer type.
141
   * @param event Event to pass to the editor.
142
   * @param consumer Consumer to pass to the editor.
143
   */
144
  public <T extends Event, U extends T> void addEventListener(
145
    final EventPattern<? super T, ? extends U> event,
146
    final Consumer<? super U> consumer ) {
147
    getActiveFileEditor().addEventListener( event, consumer );
148
  }
149
150
  /**
151
   * Delegates to the active file editor pane, and, ultimately, to its text
152
   * area.
153
   *
154
   * @param map The map of methods to events.
155
   */
156
  public void addEventListener( final InputMap<InputEvent> map ) {
157
    getActiveFileEditor().addEventListener( map );
158
  }
159
160
  /**
161
   * Remove a keyboard event listener from the active file editor.
162
   *
163
   * @param map The keyboard events to remove.
164
   */
165
  public void removeEventListener( final InputMap<InputEvent> map ) {
166
    getActiveFileEditor().removeEventListener( map );
167
  }
168
169
  /**
170
   * Allows observers to be notified when the current file editor tab changes.
171
   *
172
   * @param listener The listener to notify of tab change events.
173
   */
174
  public void addTabSelectionListener( final ChangeListener<Tab> listener ) {
175
    // Observe the tab so that when a new tab is opened or selected,
176
    // a notification is kicked off.
177
    getSelectionModel().selectedItemProperty().addListener( listener );
178
  }
179
180
  /**
181
   * Allows clients to manipulate the editor content directly.
182
   *
183
   * @return The text area for the active file editor.
184
   */
185
  public StyledTextArea getEditor() {
186
    return getActiveFileEditor().getEditorPane().getEditor();
187
  }
188
189
  public FileEditorTab getActiveFileEditor() {
190
    return this.activeFileEditor.get();
191
  }
192
193
  public ReadOnlyObjectProperty<FileEditorTab> activeFileEditorProperty() {
194
    return this.activeFileEditor.getReadOnlyProperty();
195
  }
196
197
  ReadOnlyBooleanProperty anyFileEditorModifiedProperty() {
198
    return this.anyFileEditorModified.getReadOnlyProperty();
199
  }
200
201
  private FileEditorTab createFileEditor( final Path path ) {
202
    final FileEditorTab tab = new FileEditorTab( path );
203
204
    tab.setOnCloseRequest( e -> {
205
      if( !canCloseEditor( tab ) ) {
206
        e.consume();
207
      }
208
    } );
209
210
    return tab;
211
  }
212
213
  /**
214
   * Called when the user selects New from the File menu.
215
   *
216
   * @return The newly added tab.
217
   */
218
  void newEditor() {
219
    final FileEditorTab tab = createFileEditor( null );
220
221
    getTabs().add( tab );
222
    getSelectionModel().select( tab );
223
  }
224
225
  void openFileDialog() {
226
    final String title = get( "Dialog.file.choose.open.title" );
227
    final FileChooser dialog = createFileChooser( title );
228
    openFiles( dialog.showOpenMultipleDialog( getWindow() ) );
229
  }
230
231
  /**
232
   * Opens the files into new editors, unless one of those files was a
233
   * definition file. The definition file is loaded into the definition pane,
234
   * but only the first one selected (multiple definition files will result in a
235
   * warning).
236
   *
237
   * @param files The list of non-definition files that the were requested to
238
   * open.
239
   *
240
   * @return A list of files that can be opened in text editors.
241
   */
242
  private void openFiles( final List<File> files ) {
243
    final FileTypePredicate predicate
244
      = new FileTypePredicate( createExtensionFilter( "definition" ).getExtensions() );
245
246
    // The user might have opened multiple definitions files. These will
247
    // be discarded from the text editable files.
248
    final List<File> definitions
249
      = files.stream().filter( predicate ).collect( Collectors.toList() );
250
251
    // Create a modifiable list to remove any definition files that were
252
    // opened.
253
    final List<File> editors = new ArrayList<>( files );
254
255
    if( editors.size() > 0 ) {
256
      saveLastDirectory( editors.get( 0 ) );
257
    }
258
259
    editors.removeAll( definitions );
260
261
    // Open editor-friendly files (e.g,. Markdown, XML) in new tabs.
262
    if( editors.size() > 0 ) {
263
      openEditors( editors, 0 );
264
    }
265
266
    if( definitions.size() > 0 ) {
267
      openDefinition( definitions.get( 0 ) );
268
    }
269
  }
270
271
  private void openEditors( final List<File> files, final int activeIndex ) {
272
    final int fileTally = files.size();
273
    final List<Tab> tabs = getTabs();
274
275
    // Close single unmodified "Untitled" tab.
276
    if( tabs.size() == 1 ) {
277
      final FileEditorTab fileEditor = (FileEditorTab)(tabs.get( 0 ));
278
279
      if( fileEditor.getPath() == null && !fileEditor.isModified() ) {
280
        closeEditor( fileEditor, false );
281
      }
282
    }
283
284
    for( int i = 0; i < fileTally; i++ ) {
285
      final Path path = files.get( i ).toPath();
286
287
      FileEditorTab fileEditorTab = findEditor( path );
288
289
      // Only open new files.
290
      if( fileEditorTab == null ) {
291
        fileEditorTab = createFileEditor( path );
292
        getTabs().add( fileEditorTab );
293
      }
294
295
      // Select the first file in the list.
296
      if( i == activeIndex ) {
297
        getSelectionModel().select( fileEditorTab );
298
      }
299
    }
300
  }
301
302
  /**
303
   * Returns a property that changes when a new definition file is opened.
304
   *
305
   * @return The path to a definition file that was opened.
306
   */
307
  public ReadOnlyObjectProperty<Path> onOpenDefinitionFileProperty() {
308
    return getOnOpenDefinitionFile().getReadOnlyProperty();
309
  }
310
311
  private ReadOnlyObjectWrapper<Path> getOnOpenDefinitionFile() {
312
    return this.openDefinition;
313
  }
314
315
  /**
316
   * Called when the user has opened a definition file (using the file open
317
   * dialog box). This will replace the current set of definitions for the
318
   * active tab.
319
   *
320
   * @param definition The file to open.
321
   */
322
  private void openDefinition( final File definition ) {
323
    // TODO: Prevent reading this file twice when a new text document is opened.
324
    // (might be a matter of checking the value first).
325
    getOnOpenDefinitionFile().set( definition.toPath() );
326
  }
327
328
  boolean saveEditor( final FileEditorTab fileEditor ) {
329
    if( fileEditor == null || !fileEditor.isModified() ) {
330
      return true;
331
    }
332
333
    if( fileEditor.getPath() == null ) {
334
      getSelectionModel().select( fileEditor );
335
336
      final FileChooser fileChooser = createFileChooser( Messages.get( "Dialog.file.choose.save.title" ) );
337
      final File file = fileChooser.showSaveDialog( getWindow() );
338
      if( file == null ) {
339
        return false;
340
      }
341
342
      saveLastDirectory( file );
343
      fileEditor.setPath( file.toPath() );
344
    }
345
346
    return fileEditor.save();
347
  }
348
349
  boolean saveAllEditors() {
350
    boolean success = true;
351
352
    for( FileEditorTab fileEditor : getAllEditors() ) {
353
      if( !saveEditor( fileEditor ) ) {
354
        success = false;
355
      }
356
    }
357
358
    return success;
359
  }
360
361
  /**
362
   * Answers whether the file has had modifications. '
363
   *
364
   * @param tab THe tab to check for modifications.
365
   *
366
   * @return false The file is unmodified.
367
   */
368
  boolean canCloseEditor( final FileEditorTab tab ) {
369
    if( !tab.isModified() ) {
370
      return true;
371
    }
372
373
    final AlertMessage message = getAlertService().createAlertMessage(
374
      Messages.get( "Alert.file.close.title" ),
375
      Messages.get( "Alert.file.close.text" ),
376
      tab.getText()
377
    );
378
379
    final Alert alert = getAlertService().createAlertConfirmation( message );
380
    final ButtonType response = alert.showAndWait().get();
381
382
    return response == YES ? saveEditor( tab ) : response == NO;
383
  }
384
385
  private AlertService getAlertService() {
386
    return this.alertService;
387
  }
388
389
  boolean closeEditor( FileEditorTab fileEditor, boolean save ) {
390
    if( fileEditor == null ) {
391
      return true;
392
    }
393
394
    final Tab tab = fileEditor;
395
396
    if( save ) {
397
      Event event = new Event( tab, tab, Tab.TAB_CLOSE_REQUEST_EVENT );
398
      Event.fireEvent( tab, event );
399
400
      if( event.isConsumed() ) {
401
        return false;
402
      }
403
    }
404
405
    getTabs().remove( tab );
406
407
    if( tab.getOnClosed() != null ) {
408
      Event.fireEvent( tab, new Event( Tab.CLOSED_EVENT ) );
409
    }
410
411
    return true;
412
  }
413
414
  boolean closeAllEditors() {
415
    final FileEditorTab[] allEditors = getAllEditors();
416
    final FileEditorTab activeEditor = getActiveFileEditor();
417
418
    // try to save active tab first because in case the user decides to cancel,
419
    // then it stays active
420
    if( activeEditor != null && !canCloseEditor( activeEditor ) ) {
421
      return false;
422
    }
423
424
    // This should be called any time a tab changes.
425
    persistPreferences();
426
427
    // save modified tabs
428
    for( int i = 0; i < allEditors.length; i++ ) {
429
      final FileEditorTab fileEditor = allEditors[ i ];
430
431
      if( fileEditor == activeEditor ) {
432
        continue;
433
      }
434
435
      if( fileEditor.isModified() ) {
436
        // activate the modified tab to make its modified content visible to the user
437
        getSelectionModel().select( i );
438
439
        if( !canCloseEditor( fileEditor ) ) {
440
          return false;
441
        }
442
      }
443
    }
444
445
    // Close all tabs.
446
    for( final FileEditorTab fileEditor : allEditors ) {
447
      if( !closeEditor( fileEditor, false ) ) {
448
        return false;
449
      }
450
    }
451
452
    return getTabs().isEmpty();
453
  }
454
455
  private FileEditorTab[] getAllEditors() {
456
    final ObservableList<Tab> tabs = getTabs();
457
    final int length = tabs.size();
458
    final FileEditorTab[] allEditors = new FileEditorTab[ length ];
459
460
    for( int i = 0; i < length; i++ ) {
461
      allEditors[ i ] = (FileEditorTab)tabs.get( i );
462
    }
463
464
    return allEditors;
465
  }
466
467
  /**
468
   * Returns the file editor tab that has the given path.
469
   *
470
   * @return null No file editor tab for the given path was found.
471
   */
472
  private FileEditorTab findEditor( final Path path ) {
473
    for( final Tab tab : getTabs() ) {
474
      final FileEditorTab fileEditor = (FileEditorTab)tab;
475
476
      if( fileEditor.isPath( path ) ) {
477
        return fileEditor;
478
      }
479
    }
480
481
    return null;
482
  }
483
484
  private FileChooser createFileChooser( String title ) {
485
    final FileChooser fileChooser = new FileChooser();
486
487
    fileChooser.setTitle( title );
488
    fileChooser.getExtensionFilters().addAll(
489
      createExtensionFilters() );
490
491
    final String lastDirectory = getState().get( "lastDirectory", null );
492
    File file = new File( (lastDirectory != null) ? lastDirectory : "." );
493
494
    if( !file.isDirectory() ) {
495
      file = new File( "." );
496
    }
497
498
    fileChooser.setInitialDirectory( file );
499
    return fileChooser;
500
  }
501
502
  private List<ExtensionFilter> createExtensionFilters() {
503
    final List<ExtensionFilter> list = new ArrayList<>();
504
505
    // TODO: Return a list of all properties that match the filter prefix.
506
    // This will allow dynamic filters to be added and removed just by
507
    // updating the properties file.
508
    list.add( createExtensionFilter( "markdown" ) );
509
    list.add( createExtensionFilter( "definition" ) );
510
    list.add( createExtensionFilter( "xml" ) );
511
    list.add( createExtensionFilter( "all" ) );
512
    return list;
513
  }
514
515
  private ExtensionFilter createExtensionFilter( final String filetype ) {
516
    final String tKey = String.format( "%s.title.%s", FILTER_EXTENSION_TITLES, filetype );
517
    final String eKey = String.format( "%s.ext.%s", FILTER_EXTENSIONS, filetype );
518
519
    return new ExtensionFilter( Messages.get( tKey ), getExtensions( eKey ) );
520
  }
521
522
  private List<String> getExtensions( final String key ) {
523
    return getSettings().getStringSettingList( key );
524
  }
525
526
  private void saveLastDirectory( final File file ) {
527
    getState().put( "lastDirectory", file.getParent() );
528
  }
529
530
  public void restorePreferences() {
531
    int activeIndex = 0;
532
533
    final Preferences preferences = getState();
534
    final String[] fileNames = Utils.getPrefsStrings( preferences, "file" );
535
    final String activeFileName = preferences.get( "activeFile", null );
536
537
    final ArrayList<File> files = new ArrayList<>( fileNames.length );
538
539
    for( final String fileName : fileNames ) {
540
      final File file = new File( fileName );
541
542
      if( file.exists() ) {
543
        files.add( file );
544
545
        if( fileName.equals( activeFileName ) ) {
546
          activeIndex = files.size() - 1;
547
        }
548
      }
549
    }
550
551
    if( files.isEmpty() ) {
552
      newEditor();
553
    } else {
554
      openEditors( files, activeIndex );
555
    }
556
  }
557
558
  public void persistPreferences() {
559
    final ObservableList<Tab> allEditors = getTabs();
560
    final List<String> fileNames = new ArrayList<>( allEditors.size() );
561
562
    for( final Tab tab : allEditors ) {
563
      final FileEditorTab fileEditor = (FileEditorTab)tab;
564
      final Path filePath = fileEditor.getPath();
565
566
      if( filePath != null ) {
567
        fileNames.add( filePath.toString() );
568
      }
569
    }
570
571
    final Preferences preferences = getState();
572
    Utils.putPrefsStrings( preferences, "file", fileNames.toArray( new String[ fileNames.size() ] ) );
573
574
    final FileEditorTab activeEditor = getActiveFileEditor();
575
    final Path filePath = activeEditor == null ? null : activeEditor.getPath();
576
577
    if( filePath == null ) {
578
      preferences.remove( "activeFile" );
579
    } else {
580
      preferences.put( "activeFile", filePath.toString() );
581
    }
582
  }
583
584
  private Settings getSettings() {
585
    return this.settings;
586
  }
587
588
  protected Options getOptions() {
589
    return this.options;
590
  }
591
592
  private Window getWindow() {
593
    return getScene().getWindow();
594
  }
595
596
  protected Preferences getState() {
597
    return getOptions().getState();
598
  }
599
600
  Node getNode() {
601
    return this;
602
  }
603
}
1604
A src/main/java/com/scrivenvar/Main.java
1
/*
2
 * Copyright 2016 Karl Tauber and White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar;
29
30
import static com.scrivenvar.Constants.*;
31
import com.scrivenvar.service.Options;
32
import com.scrivenvar.service.events.AlertService;
33
import com.scrivenvar.util.StageState;
34
import javafx.application.Application;
35
import javafx.scene.Scene;
36
import javafx.scene.image.Image;
37
import javafx.stage.Stage;
38
39
/**
40
 * Main application entry point. The application allows users to edit Markdown
41
 * files and see a real-time preview of the edits.
42
 *
43
 * @author Karl Tauber and White Magic Software, Ltd.
44
 */
45
public final class Main extends Application {
46
47
  private static Application app;
48
49
  private final MainWindow mainWindow = new MainWindow();
50
  private final Options options = Services.load( Options.class );
51
52
  public static void main( String[] args ) {
53
    launch( args );
54
  }
55
56
  /**
57
   * Application entry point.
58
   *
59
   * @param stage The primary application stage.
60
   *
61
   * @throws Exception Could not read configuration file.
62
   */
63
  @Override
64
  public void start( final Stage stage ) throws Exception {
65
    initApplication();
66
    initState( stage );
67
    initStage( stage );
68
    initAlertService();
69
70
    stage.show();
71
  }
72
73
  private void initApplication() {
74
    app = this;
75
  }
76
77
  private Options getOptions() {
78
    return this.options;
79
  }
80
81
  private String getApplicationTitle() {
82
    return Messages.get( "Main.title" );
83
  }
84
85
  private StageState initState( Stage stage ) {
86
    return new StageState( stage, getOptions().getState() );
87
  }
88
89
  private void initStage( Stage stage ) {
90
    stage.getIcons().addAll(
91
      createImage( FILE_LOGO_16 ),
92
      createImage( FILE_LOGO_32 ),
93
      createImage( FILE_LOGO_128 ),
94
      createImage( FILE_LOGO_256 ),
95
      createImage( FILE_LOGO_512 ) );
96
    stage.setTitle( getApplicationTitle() );
97
    stage.setScene( getScene() );
98
  }
99
100
  private void initAlertService() {
101
    final AlertService service = Services.load( AlertService.class );
102
    service.setWindow( getScene().getWindow() );
103
  }
104
105
  private Scene getScene() {
106
    return getMainWindow().getScene();
107
  }
108
109
  private MainWindow getMainWindow() {
110
    return this.mainWindow;
111
  }
112
113
  private static Application getApplication() {
114
    return app;
115
  }
116
117
  public static void showDocument( String uri ) {
118
    getApplication().getHostServices().showDocument( uri );
119
  }
120
121
  private Image createImage( final String filename ) {
122
    return new Image( filename );
123
  }
124
}
1125
A src/main/java/com/scrivenvar/MainWindow.java
1
/*
2
 * Copyright 2016 Karl Tauber and White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar;
29
30
import static com.scrivenvar.Constants.FILE_LOGO_32;
31
import static com.scrivenvar.Constants.STYLESHEET_SCENE;
32
import static com.scrivenvar.Messages.get;
33
import com.scrivenvar.definition.DefinitionFactory;
34
import com.scrivenvar.definition.DefinitionPane;
35
import com.scrivenvar.definition.DefinitionSource;
36
import com.scrivenvar.editors.VariableNameInjector;
37
import com.scrivenvar.editors.markdown.MarkdownEditorPane;
38
import com.scrivenvar.preview.HTMLPreviewPane;
39
import com.scrivenvar.processors.HTMLPreviewProcessor;
40
import com.scrivenvar.processors.MarkdownCaretInsertionProcessor;
41
import com.scrivenvar.processors.MarkdownCaretReplacementProcessor;
42
import com.scrivenvar.processors.MarkdownProcessor;
43
import com.scrivenvar.processors.Processor;
44
import com.scrivenvar.processors.VariableProcessor;
45
import com.scrivenvar.service.Options;
46
import com.scrivenvar.util.Action;
47
import com.scrivenvar.util.ActionUtils;
48
import static com.scrivenvar.util.StageState.K_PANE_SPLIT_DEFINITION;
49
import static com.scrivenvar.util.StageState.K_PANE_SPLIT_EDITOR;
50
import static com.scrivenvar.util.StageState.K_PANE_SPLIT_PREVIEW;
51
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.BOLD;
52
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.CODE;
53
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FILE_ALT;
54
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FILE_CODE_ALT;
55
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FLOPPY_ALT;
56
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FOLDER_OPEN_ALT;
57
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.HEADER;
58
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.ITALIC;
59
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LINK;
60
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LIST_OL;
61
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LIST_UL;
62
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.PICTURE_ALT;
63
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.QUOTE_LEFT;
64
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.REPEAT;
65
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.STRIKETHROUGH;
66
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.UNDO;
67
import java.io.File;
68
import java.nio.file.Path;
69
import java.util.HashMap;
70
import java.util.Map;
71
import java.util.function.Function;
72
import java.util.prefs.Preferences;
73
import javafx.beans.binding.Bindings;
74
import javafx.beans.binding.BooleanBinding;
75
import javafx.beans.property.BooleanProperty;
76
import javafx.beans.property.SimpleBooleanProperty;
77
import javafx.beans.value.ObservableBooleanValue;
78
import javafx.beans.value.ObservableValue;
79
import javafx.collections.ListChangeListener.Change;
80
import javafx.collections.ObservableList;
81
import javafx.event.Event;
82
import javafx.scene.Node;
83
import javafx.scene.Scene;
84
import javafx.scene.control.Alert;
85
import javafx.scene.control.Alert.AlertType;
86
import javafx.scene.control.Menu;
87
import javafx.scene.control.MenuBar;
88
import javafx.scene.control.SplitPane;
89
import javafx.scene.control.Tab;
90
import javafx.scene.control.ToolBar;
91
import javafx.scene.control.TreeView;
92
import javafx.scene.image.Image;
93
import javafx.scene.image.ImageView;
94
import static javafx.scene.input.KeyCode.ESCAPE;
95
import javafx.scene.input.KeyEvent;
96
import static javafx.scene.input.KeyEvent.CHAR_UNDEFINED;
97
import static javafx.scene.input.KeyEvent.KEY_PRESSED;
98
import javafx.scene.layout.BorderPane;
99
import javafx.scene.layout.VBox;
100
import javafx.stage.Window;
101
import javafx.stage.WindowEvent;
102
103
/**
104
 * Main window containing a tab pane in the center for file editors.
105
 *
106
 * @author Karl Tauber and White Magic Software, Ltd.
107
 */
108
public class MainWindow {
109
110
  private final Options options = Services.load( Options.class );
111
112
  private Scene scene;
113
114
  private DefinitionPane definitionPane;
115
  private FileEditorTabPane fileEditorPane;
116
  private HTMLPreviewPane previewPane;
117
118
  private VariableNameInjector variableNameInjector;
119
120
  private MenuBar menuBar;
121
122
  public MainWindow() {
123
    initLayout();
124
    initOpenDefinitionListener();
125
    initTabAddedListener();
126
    initTabChangeListener();
127
    initPreferences();
128
    initVariableNameInjector();
129
  }
130
131
  /**
132
   * Listen for file editor tab pane to receive an open definition source event.
133
   */
134
  private void initOpenDefinitionListener() {
135
    getFileEditorPane().onOpenDefinitionFileProperty().addListener(
136
      (ObservableValue<? extends Path> definitionFile,
137
        final Path oldPath, final Path newPath) -> {
138
        final DefinitionSource ds = createDefinitionSource( newPath );
139
        associate( ds, getActiveFileEditor() );
140
      } );
141
  }
142
143
  /**
144
   * When tabs are added, hook the various change listeners onto the new tab so
145
   * that the preview pane refreshes as necessary.
146
   */
147
  private void initTabAddedListener() {
148
    final FileEditorTabPane editorPane = getFileEditorPane();
149
150
    // Make sure the text processor kicks off when new files are opened.
151
    final ObservableList<Tab> tabs = editorPane.getTabs();
152
153
    // Update the preview pane on tab changes.
154
    tabs.addListener(
155
      (final Change<? extends Tab> change) -> {
156
        while( change.next() ) {
157
          if( change.wasAdded() ) {
158
            // Multiple tabs can be added simultaneously.
159
            for( final Tab newTab : change.getAddedSubList() ) {
160
              final FileEditorTab tab = (FileEditorTab)newTab;
161
162
              initTextChangeListener( tab );
163
              initCaretParagraphListener( tab );
164
            }
165
          }
166
        }
167
      }
168
    );
169
  }
170
171
  /**
172
   * Reloads the preferences from the previous load.
173
   */
174
  private void initPreferences() {
175
    getFileEditorPane().restorePreferences();
176
  }
177
178
  /**
179
   * Listen for new tab selection events.
180
   */
181
  private void initTabChangeListener() {
182
    final FileEditorTabPane editorPane = getFileEditorPane();
183
184
    // Update the preview pane changing tabs.
185
    editorPane.addTabSelectionListener(
186
      (ObservableValue<? extends Tab> tabPane,
187
        final Tab oldTab, final Tab newTab) -> {
188
189
        // If there was no old tab, then this is a first time load, which
190
        // can be ignored.
191
        if( oldTab != null ) {
192
          if( newTab == null ) {
193
            closeRemainingTab();
194
          } else {
195
            // Synchronize the preview with the edited text.
196
            refreshSelectedTab( (FileEditorTab)newTab );
197
          }
198
        }
199
      }
200
    );
201
  }
202
203
  /**
204
   * Initialize the variable name editor.
205
   */
206
  private void initVariableNameInjector() {
207
    setVariableNameInjector(
208
      new VariableNameInjector( getFileEditorPane(), getDefinitionPane() )
209
    );
210
  }
211
212
  private void initTextChangeListener( final FileEditorTab tab ) {
213
    tab.addTextChangeListener(
214
      (ObservableValue<? extends String> editor,
215
        final String oldValue, final String newValue) -> {
216
        refreshSelectedTab( tab );
217
      }
218
    );
219
  }
220
221
  private void initCaretParagraphListener( final FileEditorTab tab ) {
222
    tab.addCaretParagraphListener(
223
      (ObservableValue<? extends Integer> editor,
224
        final Integer oldValue, final Integer newValue) -> {
225
        refreshSelectedTab( tab );
226
      }
227
    );
228
  }
229
230
  /**
231
   * Called whenever the preview pane becomes out of sync with the file editor
232
   * tab. This can be called when the text changes, the caret paragraph changes,
233
   * or the file tab changes.
234
   *
235
   * @param tab The file editor tab that has been changed in some fashion.
236
   */
237
  private void refreshSelectedTab( final FileEditorTab tab ) {
238
    final HTMLPreviewPane preview = getPreviewPane();
239
    preview.setPath( tab.getPath() );
240
241
    final Processor<String> hpp = new HTMLPreviewProcessor( preview );
242
    final Processor<String> mcrp = new MarkdownCaretReplacementProcessor( hpp );
243
    final Processor<String> mp = new MarkdownProcessor( mcrp );
244
    final Processor<String> mcip = new MarkdownCaretInsertionProcessor( mp, tab.getCaretPosition() );
245
    final Processor<String> vp = new VariableProcessor( mcip, getResolvedMap() );
246
247
    vp.processChain( tab.getEditorText() );
248
  }
249
250
  /**
251
   * TODO: Patch into loading of definition source.
252
   *
253
   * @return
254
   */
255
  private Map<String, String> getResolvedMap() {
256
    return new HashMap<>();
257
  }
258
259
  /**
260
   * TODO: Patch into loading of definition source.
261
   *
262
   * @return
263
   */
264
  private TreeView<String> getTreeView() {
265
    return new TreeView<>();
266
  }
267
268
  /**
269
   * Called when the tab has changed to a new editor to replace the current
270
   * definition pane with the
271
   *
272
   * @param tab Reference to the tab that has the file being edited.
273
   */
274
  private void updateDefinitionPane( final FileEditorTab tab ) {
275
    // Look up the path to the variable definition file associated with the
276
    // given tab.
277
    final Path path = getVariableDefinitionPath( tab.getPath() );
278
    final DefinitionSource ds = createDefinitionSource( path );
279
280
    associate( ds, tab );
281
  }
282
283
  private void associate( final DefinitionSource ds, final FileEditorTab tab ) {
284
    System.out.println( "Associate " + ds + " with " + tab );
285
  }
286
287
  /**
288
   * Searches the persistent settings for the variable definition file that is
289
   * associated with the given path.
290
   *
291
   * @param tabPath The path that may be associated with some variables.
292
   *
293
   * @return A path to the variable definition file for the given document path.
294
   */
295
  private Path getVariableDefinitionPath( final Path tabPath ) {
296
    return new File( "/tmp/variables.yaml" ).toPath();
297
  }
298
299
  /**
300
   * Creates a boolean property that is bound to another boolean value of the
301
   * active editor.
302
   */
303
  private BooleanProperty createActiveBooleanProperty(
304
    final Function<FileEditorTab, ObservableBooleanValue> func ) {
305
306
    final BooleanProperty b = new SimpleBooleanProperty();
307
    final FileEditorTab tab = getActiveFileEditor();
308
309
    if( tab != null ) {
310
      b.bind( func.apply( tab ) );
311
    }
312
313
    getFileEditorPane().activeFileEditorProperty().addListener(
314
      (observable, oldFileEditor, newFileEditor) -> {
315
        b.unbind();
316
317
        if( newFileEditor != null ) {
318
          b.bind( func.apply( newFileEditor ) );
319
        } else {
320
          b.set( false );
321
        }
322
      }
323
    );
324
325
    return b;
326
  }
327
328
  /**
329
   * Called when the last open tab is closed. This clears out the preview pane
330
   * and the definition pane.
331
   */
332
  private void closeRemainingTab() {
333
    getPreviewPane().clear();
334
    getDefinitionPane().clear();
335
  }
336
337
  //---- File actions -------------------------------------------------------
338
  private void fileNew() {
339
    getFileEditorPane().newEditor();
340
  }
341
342
  private void fileOpen() {
343
    getFileEditorPane().openFileDialog();
344
  }
345
346
  private void fileClose() {
347
    getFileEditorPane().closeEditor( getActiveFileEditor(), true );
348
  }
349
350
  private void fileCloseAll() {
351
    getFileEditorPane().closeAllEditors();
352
  }
353
354
  private void fileSave() {
355
    getFileEditorPane().saveEditor( getActiveFileEditor() );
356
  }
357
358
  private void fileSaveAll() {
359
    getFileEditorPane().saveAllEditors();
360
  }
361
362
  private void fileExit() {
363
    final Window window = getWindow();
364
    Event.fireEvent( window,
365
      new WindowEvent( window, WindowEvent.WINDOW_CLOSE_REQUEST ) );
366
  }
367
368
  //---- Help actions -------------------------------------------------------
369
  private void helpAbout() {
370
    Alert alert = new Alert( AlertType.INFORMATION );
371
    alert.setTitle( get( "Dialog.about.title" ) );
372
    alert.setHeaderText( get( "Dialog.about.header" ) );
373
    alert.setContentText( get( "Dialog.about.content" ) );
374
    alert.setGraphic( new ImageView( new Image( FILE_LOGO_32 ) ) );
375
    alert.initOwner( getWindow() );
376
377
    alert.showAndWait();
378
  }
379
380
  //---- Convenience accessors ----------------------------------------------
381
  private float getFloat( final String key, final float defaultValue ) {
382
    return getPreferences().getFloat( key, defaultValue );
383
  }
384
385
  private Preferences getPreferences() {
386
    return getOptions().getState();
387
  }
388
389
  private Window getWindow() {
390
    return getScene().getWindow();
391
  }
392
393
  private MarkdownEditorPane getActiveEditor() {
394
    return (MarkdownEditorPane)(getActiveFileEditor().getEditorPane());
395
  }
396
397
  private FileEditorTab getActiveFileEditor() {
398
    return getFileEditorPane().getActiveFileEditor();
399
  }
400
401
  //---- Member accessors ---------------------------------------------------
402
  public Scene getScene() {
403
    return this.scene;
404
  }
405
406
  private void setScene( Scene scene ) {
407
    this.scene = scene;
408
  }
409
410
  private FileEditorTabPane getFileEditorPane() {
411
    if( this.fileEditorPane == null ) {
412
      this.fileEditorPane = createFileEditorPane();
413
    }
414
415
    return this.fileEditorPane;
416
  }
417
418
  private synchronized HTMLPreviewPane getPreviewPane() {
419
    if( this.previewPane == null ) {
420
      this.previewPane = createPreviewPane();
421
    }
422
423
    return this.previewPane;
424
  }
425
426
  private DefinitionPane getDefinitionPane() {
427
    if( this.definitionPane == null ) {
428
      this.definitionPane = createDefinitionPane();
429
    }
430
431
    return this.definitionPane;
432
  }
433
434
  public VariableNameInjector getVariableNameInjector() {
435
    return this.variableNameInjector;
436
  }
437
438
  public void setVariableNameInjector( final VariableNameInjector injector ) {
439
    this.variableNameInjector = injector;
440
  }
441
442
  private Options getOptions() {
443
    return this.options;
444
  }
445
446
  public MenuBar getMenuBar() {
447
    return this.menuBar;
448
  }
449
450
  public void setMenuBar( MenuBar menuBar ) {
451
    this.menuBar = menuBar;
452
  }
453
454
  //---- Member creators ----------------------------------------------------
455
  private DefinitionSource createDefinitionSource( final Path path ) {
456
    return createDefinitionFactory().fileDefinitionSource( path );
457
  }
458
459
  /**
460
   * Create an editor pane to hold file editor tabs.
461
   *
462
   * @return A new instance, never null.
463
   */
464
  private FileEditorTabPane createFileEditorPane() {
465
    return new FileEditorTabPane();
466
  }
467
468
  private HTMLPreviewPane createPreviewPane() {
469
    return new HTMLPreviewPane();
470
  }
471
472
  protected DefinitionPane createDefinitionPane() {
473
    return new DefinitionPane( getTreeView() );
474
  }
475
476
  private DefinitionFactory createDefinitionFactory() {
477
    return new DefinitionFactory();
478
  }
479
480
  private Node createMenuBar() {
481
    final BooleanBinding activeFileEditorIsNull = getFileEditorPane().activeFileEditorProperty().isNull();
482
483
    // File actions
484
    Action fileNewAction = new Action( get( "Main.menu.file.new" ), "Shortcut+N", FILE_ALT, e -> fileNew() );
485
    Action fileOpenAction = new Action( get( "Main.menu.file.open" ), "Shortcut+O", FOLDER_OPEN_ALT, e -> fileOpen() );
486
    Action fileCloseAction = new Action( get( "Main.menu.file.close" ), "Shortcut+W", null, e -> fileClose(), activeFileEditorIsNull );
487
    Action fileCloseAllAction = new Action( get( "Main.menu.file.close_all" ), null, null, e -> fileCloseAll(), activeFileEditorIsNull );
488
    Action fileSaveAction = new Action( get( "Main.menu.file.save" ), "Shortcut+S", FLOPPY_ALT, e -> fileSave(),
489
      createActiveBooleanProperty( FileEditorTab::modifiedProperty ).not() );
490
    Action fileSaveAllAction = new Action( get( "Main.menu.file.save_all" ), "Shortcut+Shift+S", null, e -> fileSaveAll(),
491
      Bindings.not( getFileEditorPane().anyFileEditorModifiedProperty() ) );
492
    Action fileExitAction = new Action( get( "Main.menu.file.exit" ), null, null, e -> fileExit() );
493
494
    // Edit actions
495
    Action editUndoAction = new Action( get( "Main.menu.edit.undo" ), "Shortcut+Z", UNDO,
496
      e -> getActiveEditor().undo(),
497
      createActiveBooleanProperty( FileEditorTab::canUndoProperty ).not() );
498
    Action editRedoAction = new Action( get( "Main.menu.edit.redo" ), "Shortcut+Y", REPEAT,
499
      e -> getActiveEditor().redo(),
500
      createActiveBooleanProperty( FileEditorTab::canRedoProperty ).not() );
501
502
    // Insert actions
503
    Action insertBoldAction = new Action( get( "Main.menu.insert.bold" ), "Shortcut+B", BOLD,
504
      e -> getActiveEditor().surroundSelection( "**", "**" ),
505
      activeFileEditorIsNull );
506
    Action insertItalicAction = new Action( get( "Main.menu.insert.italic" ), "Shortcut+I", ITALIC,
507
      e -> getActiveEditor().surroundSelection( "*", "*" ),
508
      activeFileEditorIsNull );
509
    Action insertStrikethroughAction = new Action( get( "Main.menu.insert.strikethrough" ), "Shortcut+T", STRIKETHROUGH,
510
      e -> getActiveEditor().surroundSelection( "~~", "~~" ),
511
      activeFileEditorIsNull );
512
    Action insertBlockquoteAction = new Action( get( "Main.menu.insert.blockquote" ), "Ctrl+Q", QUOTE_LEFT, // not Shortcut+Q because of conflict on Mac
513
      e -> getActiveEditor().surroundSelection( "\n\n> ", "" ),
514
      activeFileEditorIsNull );
515
    Action insertCodeAction = new Action( get( "Main.menu.insert.code" ), "Shortcut+K", CODE,
516
      e -> getActiveEditor().surroundSelection( "`", "`" ),
517
      activeFileEditorIsNull );
518
    Action insertFencedCodeBlockAction = new Action( get( "Main.menu.insert.fenced_code_block" ), "Shortcut+Shift+K", FILE_CODE_ALT,
519
      e -> getActiveEditor().surroundSelection( "\n\n```\n", "\n```\n\n", get( "Main.menu.insert.fenced_code_block.prompt" ) ),
520
      activeFileEditorIsNull );
521
522
    Action insertLinkAction = new Action( get( "Main.menu.insert.link" ), "Shortcut+L", LINK,
523
      e -> getActiveEditor().insertLink(),
524
      activeFileEditorIsNull );
525
    Action insertImageAction = new Action( get( "Main.menu.insert.image" ), "Shortcut+G", PICTURE_ALT,
526
      e -> getActiveEditor().insertImage(),
527
      activeFileEditorIsNull );
528
529
    final Action[] headers = new Action[ 6 ];
530
531
    // Insert header actions (H1 ... H6)
532
    for( int i = 1; i <= 6; i++ ) {
533
      final String hashes = new String( new char[ i ] ).replace( "\0", "#" );
534
      final String markup = String.format( "\n\n%s ", hashes );
535
      final String text = get( "Main.menu.insert.header_" + i );
536
      final String accelerator = "Shortcut+" + i;
537
      final String prompt = get( "Main.menu.insert.header_" + i + ".prompt" );
538
539
      headers[ i - 1 ] = new Action( text, accelerator, HEADER,
540
        e -> getActiveEditor().surroundSelection( markup, "", prompt ),
541
        activeFileEditorIsNull );
542
    }
543
544
    Action insertUnorderedListAction = new Action( get( "Main.menu.insert.unordered_list" ), "Shortcut+U", LIST_UL,
545
      e -> getActiveEditor().surroundSelection( "\n\n* ", "" ),
546
      activeFileEditorIsNull );
547
    Action insertOrderedListAction = new Action( get( "Main.menu.insert.ordered_list" ), "Shortcut+Shift+O", LIST_OL,
548
      e -> getActiveEditor().surroundSelection( "\n\n1. ", "" ),
549
      activeFileEditorIsNull );
550
    Action insertHorizontalRuleAction = new Action( get( "Main.menu.insert.horizontal_rule" ), "Shortcut+H", null,
551
      e -> getActiveEditor().surroundSelection( "\n\n---\n\n", "" ),
552
      activeFileEditorIsNull );
553
554
    // Help actions
555
    Action helpAboutAction = new Action( get( "Main.menu.help.about" ), null, null, e -> helpAbout() );
556
557
    //---- MenuBar ----
558
    Menu fileMenu = ActionUtils.createMenu( get( "Main.menu.file" ),
559
      fileNewAction,
560
      fileOpenAction,
561
      null,
562
      fileCloseAction,
563
      fileCloseAllAction,
564
      null,
565
      fileSaveAction,
566
      fileSaveAllAction,
567
      null,
568
      fileExitAction );
569
570
    Menu editMenu = ActionUtils.createMenu( get( "Main.menu.edit" ),
571
      editUndoAction,
572
      editRedoAction );
573
574
    Menu insertMenu = ActionUtils.createMenu( get( "Main.menu.insert" ),
575
      insertBoldAction,
576
      insertItalicAction,
577
      insertStrikethroughAction,
578
      insertBlockquoteAction,
579
      insertCodeAction,
580
      insertFencedCodeBlockAction,
581
      null,
582
      insertLinkAction,
583
      insertImageAction,
584
      null,
585
      headers[ 0 ],
586
      headers[ 1 ],
587
      headers[ 2 ],
588
      headers[ 3 ],
589
      headers[ 4 ],
590
      headers[ 5 ],
591
      null,
592
      insertUnorderedListAction,
593
      insertOrderedListAction,
594
      insertHorizontalRuleAction );
595
596
    Menu helpMenu = ActionUtils.createMenu( get( "Main.menu.help" ),
597
      helpAboutAction );
598
599
    menuBar = new MenuBar( fileMenu, editMenu, insertMenu, helpMenu );
600
601
    //---- ToolBar ----
602
    ToolBar toolBar = ActionUtils.createToolBar(
603
      fileNewAction,
604
      fileOpenAction,
605
      fileSaveAction,
606
      null,
607
      editUndoAction,
608
      editRedoAction,
609
      null,
610
      insertBoldAction,
611
      insertItalicAction,
612
      insertBlockquoteAction,
613
      insertCodeAction,
614
      insertFencedCodeBlockAction,
615
      null,
616
      insertLinkAction,
617
      insertImageAction,
618
      null,
619
      headers[ 0 ],
620
      null,
621
      insertUnorderedListAction,
622
      insertOrderedListAction );
623
624
    return new VBox( menuBar, toolBar );
625
  }
626
627
  private void initLayout() {
628
    final SplitPane splitPane = new SplitPane(
629
      getDefinitionPane().getNode(),
630
      getFileEditorPane().getNode(),
631
      getPreviewPane().getNode() );
632
633
    splitPane.setDividerPositions(
634
      getFloat( K_PANE_SPLIT_DEFINITION, .10f ),
635
      getFloat( K_PANE_SPLIT_EDITOR, .45f ),
636
      getFloat( K_PANE_SPLIT_PREVIEW, .45f ) );
637
638
    // See: http://broadlyapplicable.blogspot.ca/2015/03/javafx-capture-restorePreferences-splitpane.html
639
    final BorderPane borderPane = new BorderPane();
640
    borderPane.setPrefSize( 1024, 800 );
641
    borderPane.setTop( createMenuBar() );
642
    borderPane.setCenter( splitPane );
643
644
    final Scene appScene = new Scene( borderPane );
645
    setScene( appScene );
646
    appScene.getStylesheets().add( STYLESHEET_SCENE );
647
    appScene.windowProperty().addListener(
648
      (observable, oldWindow, newWindow) -> {
649
        newWindow.setOnCloseRequest( e -> {
650
          if( !getFileEditorPane().closeAllEditors() ) {
651
            e.consume();
652
          }
653
        } );
654
655
        // Workaround JavaFX bug: deselect menubar if window loses focus.
656
        newWindow.focusedProperty().addListener(
657
          (obs, oldFocused, newFocused) -> {
658
            if( !newFocused ) {
659
              // Send an ESC key event to the menubar
660
              this.menuBar.fireEvent(
661
                new KeyEvent(
662
                  KEY_PRESSED, CHAR_UNDEFINED, "", ESCAPE,
663
                  false, false, false, false ) );
664
            }
665
          }
666
        );
667
      }
668
    );
669
  }
670
}
1671
A src/main/java/com/scrivenvar/Messages.java
1
/*
2
 * Copyright (c) 2016 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  * Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  * Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
package com.scrivenvar;
28
29
import java.text.MessageFormat;
30
import java.util.ResourceBundle;
31
import java.util.Stack;
32
import static com.scrivenvar.Constants.APP_BUNDLE_NAME;
33
34
/**
35
 * Recursively resolves message properties. Property values can refer to other
36
 * properties using a <code>${var}</code> syntax.
37
 *
38
 * @author Karl Tauber, Dave Jarvis
39
 */
40
public class Messages {
41
42
  private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(APP_BUNDLE_NAME );
43
44
  private Messages() {
45
  }
46
47
  /**
48
   * Return the value of a resource bundle value after having resolved any
49
   * references to other bundle variables.
50
   *
51
   * @param props The bundle containing resolvable properties.
52
   * @param s The value for a key to resolve.
53
   *
54
   * @return The value of the key with all references recursively dereferenced.
55
   */
56
  private static String resolve( ResourceBundle props, String s ) {
57
    final int len = s.length();
58
    final Stack<StringBuilder> stack = new Stack<>();
59
60
    StringBuilder sb = new StringBuilder( 256 );
61
    boolean open = false;
62
63
    for( int i = 0; i < len; i++ ) {
64
      final char c = s.charAt( i );
65
66
      switch( c ) {
67
        case '$': {
68
          if( i + 1 < len && s.charAt( i + 1 ) == '{' ) {
69
            stack.push( sb );
70
            sb = new StringBuilder( 256 );
71
            i++;
72
            open = true;
73
          }
74
75
          break;
76
        }
77
78
        case '}': {
79
          if( open ) {
80
            open = false;
81
            final String name = sb.toString();
82
83
            sb = stack.pop();
84
            sb.append( props.getString( name ) );
85
            break;
86
          }
87
        }
88
89
        default: {
90
          sb.append( c );
91
          break;
92
        }
93
      }
94
    }
95
96
    if( open ) {
97
      throw new IllegalArgumentException( "missing '}'" );
98
    }
99
100
    return sb.toString();
101
  }
102
103
  /**
104
   * Returns the value for a key from the message bundle.
105
   *
106
   * @param key Retrieve the value for this key.
107
   *
108
   * @return The value for the key.
109
   */
110
  public static String get( String key ) {
111
    String result;
112
113
    try {
114
      result = resolve( RESOURCE_BUNDLE, RESOURCE_BUNDLE.getString( key ) );
115
    } catch( Exception e ) {
116
      
117
      // Instead of crashing, launch the application and show the resource
118
      // name.
119
      result = key;
120
    }
121
122
    return result;
123
  }
124
125
  /**
126
   * Returns the value for a key from the message bundle with the arguments
127
   * replacing <code>{#}</code> place holders.
128
   *
129
   * @param key Retrieve the value for this key.
130
   * @param args The values to substitute for place holders.
131
   *
132
   * @return The value for the key.
133
   */
134
  public static String get( String key, Object... args ) {
135
    return MessageFormat.format( get( key ), args );
136
  }
137
}
1138
A src/main/java/com/scrivenvar/Services.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar;
29
30
import java.util.ServiceLoader;
31
32
/**
33
 * Responsible for loading services.
34
 *
35
 * @author White Magic Software, Ltd.
36
 */
37
public class Services {
38
39
  /**
40
   * Loads a service based on its interface definition.
41
   *
42
   * @param <T> The service to load.
43
   * @param api The interface definition for the service.
44
   *
45
   * @return A class that implements the interface.
46
   */
47
  public static <T> T load( Class<T> api ) {
48
    final ServiceLoader<T> services = ServiceLoader.load( api );
49
    T result = null;
50
51
    for( T service : services ) {
52
      result = service;
53
54
      if( result != null ) {
55
        break;
56
      }
57
    }
58
59
    if( result == null ) {
60
      throw new RuntimeException( "No implementation for: " + api );
61
    }
62
63
    return result;
64
  }
65
}
166
A src/main/java/com/scrivenvar/controls/BrowseDirectoryButton.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
28
package com.scrivenvar.controls;
29
30
import java.io.File;
31
import javafx.event.ActionEvent;
32
import javafx.scene.control.Tooltip;
33
import javafx.stage.DirectoryChooser;
34
import com.scrivenvar.Messages;
35
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon;
36
import de.jensd.fx.glyphs.fontawesome.utils.FontAwesomeIconFactory;
37
38
/**
39
 * Button that opens a directory chooser to select a local directory for a URL in markdown.
40
 *
41
 * @author Karl Tauber
42
 */
43
public class BrowseDirectoryButton
44
	extends BrowseFileButton
45
{
46
	public BrowseDirectoryButton() {
47
		setGraphic(FontAwesomeIconFactory.get().createIcon(FontAwesomeIcon.FOLDER_ALT, "1.2em"));
48
		setTooltip(new Tooltip(Messages.get("BrowseDirectoryButton.tooltip")));
49
	}
50
51
	@Override
52
	protected void browse(ActionEvent e) {
53
		DirectoryChooser directoryChooser = new DirectoryChooser();
54
		directoryChooser.setTitle(Messages.get("BrowseDirectoryButton.chooser.title"));
55
		directoryChooser.setInitialDirectory(getInitialDirectory());
56
		File result = directoryChooser.showDialog(getScene().getWindow());
57
		if (result != null)
58
			updateUrl(result);
59
	}
60
}
161
A src/main/java/com/scrivenvar/controls/BrowseFileButton.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
28
package com.scrivenvar.controls;
29
30
import java.io.File;
31
import java.nio.file.Path;
32
import java.util.ArrayList;
33
import java.util.List;
34
import javafx.beans.property.ObjectProperty;
35
import javafx.beans.property.SimpleObjectProperty;
36
import javafx.event.ActionEvent;
37
import javafx.scene.control.Button;
38
import javafx.scene.control.Tooltip;
39
import javafx.scene.input.KeyCode;
40
import javafx.scene.input.KeyEvent;
41
import javafx.stage.FileChooser;
42
import javafx.stage.FileChooser.ExtensionFilter;
43
import com.scrivenvar.Messages;
44
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon;
45
import de.jensd.fx.glyphs.fontawesome.utils.FontAwesomeIconFactory;
46
47
/**
48
 * Button that opens a file chooser to select a local file for a URL in markdown.
49
 *
50
 * @author Karl Tauber
51
 */
52
public class BrowseFileButton
53
	extends Button
54
{
55
	private final List<ExtensionFilter> extensionFilters = new ArrayList<>();
56
57
	public BrowseFileButton() {
58
		setGraphic(FontAwesomeIconFactory.get().createIcon(FontAwesomeIcon.FILE_ALT, "1.2em"));
59
		setTooltip(new Tooltip(Messages.get("BrowseFileButton.tooltip")));
60
		setOnAction(this::browse);
61
62
		disableProperty().bind(basePath.isNull());
63
64
		// workaround for a JavaFX bug:
65
		//   avoid closing the dialog that contains this control when the user
66
		//   closes the FileChooser or DirectoryChooser using the ESC key
67
		addEventHandler(KeyEvent.KEY_RELEASED, e-> {
68
			if (e.getCode() == KeyCode.ESCAPE)
69
				e.consume();
70
		});
71
	}
72
73
	public void addExtensionFilter(ExtensionFilter extensionFilter) {
74
		extensionFilters.add(extensionFilter);
75
	}
76
77
	// 'basePath' property
78
	private final ObjectProperty<Path> basePath = new SimpleObjectProperty<>();
79
	public Path getBasePath() { return basePath.get(); }
80
	public void setBasePath(Path basePath) { this.basePath.set(basePath); }
81
	public ObjectProperty<Path> basePathProperty() { return basePath; }
82
83
	// 'url' property
84
	private final ObjectProperty<String> url = new SimpleObjectProperty<>();
85
	public String getUrl() { return url.get(); }
86
	public void setUrl(String url) { this.url.set(url); }
87
	public ObjectProperty<String> urlProperty() { return url; }
88
89
	protected void browse(ActionEvent e) {
90
		FileChooser fileChooser = new FileChooser();
91
		fileChooser.setTitle(Messages.get("BrowseFileButton.chooser.title"));
92
		fileChooser.getExtensionFilters().addAll(extensionFilters);
93
		fileChooser.getExtensionFilters().add(new ExtensionFilter(Messages.get("BrowseFileButton.chooser.allFilesFilter"), "*.*"));
94
		fileChooser.setInitialDirectory(getInitialDirectory());
95
		File result = fileChooser.showOpenDialog(getScene().getWindow());
96
		if (result != null)
97
			updateUrl(result);
98
	}
99
100
	protected File getInitialDirectory() {
101
		//TODO build initial directory based on current value of 'url' property
102
		return getBasePath().toFile();
103
	}
104
105
	protected void updateUrl(File file) {
106
		String newUrl;
107
		try {
108
			newUrl = getBasePath().relativize(file.toPath()).toString();
109
		} catch (IllegalArgumentException ex) {
110
			newUrl = file.toString();
111
		}
112
		url.set(newUrl.replace('\\', '/'));
113
	}
114
}
1115
A src/main/java/com/scrivenvar/controls/EscapeTextField.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
28
package com.scrivenvar.controls;
29
30
import javafx.beans.property.SimpleStringProperty;
31
import javafx.beans.property.StringProperty;
32
import javafx.scene.control.TextField;
33
import javafx.util.StringConverter;
34
import com.scrivenvar.util.Utils;
35
36
/**
37
 * TextField that can escape/unescape characters for markdown.
38
 *
39
 * @author Karl Tauber
40
 */
41
public class EscapeTextField
42
	extends TextField
43
{
44
	public EscapeTextField() {
45
		escapedText.bindBidirectional(textProperty(), new StringConverter<String>() {
46
			@Override public String toString(String object) { return escape(object); }
47
			@Override public String fromString(String string) { return unescape(string); }
48
		});
49
		escapeCharacters.addListener(e -> escapedText.set(escape(textProperty().get())));
50
	}
51
52
	// 'escapedText' property
53
	private final StringProperty escapedText = new SimpleStringProperty();
54
	public String getEscapedText() { return escapedText.get(); }
55
	public void setEscapedText(String escapedText) { this.escapedText.set(escapedText); }
56
	public StringProperty escapedTextProperty() { return escapedText; }
57
58
	// 'escapeCharacters' property
59
	private final StringProperty escapeCharacters = new SimpleStringProperty();
60
	public String getEscapeCharacters() { return escapeCharacters.get(); }
61
	public void setEscapeCharacters(String escapeCharacters) { this.escapeCharacters.set(escapeCharacters); }
62
	public StringProperty escapeCharactersProperty() { return escapeCharacters; }
63
64
	private String escape(String s) {
65
		String escapeChars = getEscapeCharacters();
66
		return !Utils.isNullOrEmpty(escapeChars)
67
				? s.replaceAll("([" + escapeChars.replaceAll("(.)", "\\\\$1") + "])", "\\\\$1")
68
				: s;
69
	}
70
71
	private String unescape(String s) {
72
		String escapeChars = getEscapeCharacters();
73
		return !Utils.isNullOrEmpty(escapeChars)
74
				? s.replaceAll("\\\\([" + escapeChars.replaceAll("(.)", "\\\\$1") + "])", "$1")
75
				: s;
76
	}
77
}
178
A src/main/java/com/scrivenvar/controls/FlagCheckBox.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
package com.scrivenvar.controls;
28
29
import javafx.beans.property.IntegerProperty;
30
import javafx.beans.property.SimpleIntegerProperty;
31
import javafx.scene.control.CheckBox;
32
33
/**
34
 * CheckBox that toggles a bit in an integer.
35
 *
36
 * @author Karl Tauber
37
 */
38
public class FlagCheckBox extends CheckBox {
39
40
  public FlagCheckBox() {
41
    setOnAction( e -> {
42
      if( isSelected() ) {
43
        setFlags( getFlags() | getFlag() );
44
      } else {
45
        setFlags( getFlags() & ~getFlag() );
46
      }
47
    } );
48
49
    flags.addListener( (obs, oldFlags, newFlags) -> {
50
      setSelected( (newFlags.intValue() & getFlag()) != 0 );
51
    } );
52
  }
53
54
  // 'flag' property
55
  private final IntegerProperty flag = new SimpleIntegerProperty();
56
57
  public int getFlag() {
58
    return flag.get();
59
  }
60
61
  public void setFlag( int flag ) {
62
    this.flag.set( flag );
63
  }
64
65
  public IntegerProperty flagProperty() {
66
    return flag;
67
  }
68
69
  // 'flags' property
70
  private final IntegerProperty flags = new SimpleIntegerProperty();
71
72
  public int getFlags() {
73
    return flags.get();
74
  }
75
76
  public void setFlags( int flags ) {
77
    this.flags.set( flags );
78
  }
79
80
  public IntegerProperty flagsProperty() {
81
    return flags;
82
  }
83
}
184
A src/main/java/com/scrivenvar/controls/WebHyperlink.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
28
package com.scrivenvar.controls;
29
30
import javafx.beans.property.SimpleStringProperty;
31
import javafx.beans.property.StringProperty;
32
import javafx.scene.control.Hyperlink;
33
import com.scrivenvar.Main;
34
35
/**
36
 * Opens a web site in the default web browser.
37
 *
38
 * @author Karl Tauber
39
 */
40
public class WebHyperlink
41
	extends Hyperlink
42
{
43
	public WebHyperlink() {
44
		setStyle("-fx-padding: 0; -fx-border-width: 0");
45
	}
46
47
	@Override
48
	public void fire() {
49
		Main.showDocument(getUri());
50
	}
51
52
	// 'uri' property
53
	private final StringProperty uri = new SimpleStringProperty();
54
	public String getUri() { return uri.get(); }
55
	public void setUri(String uri) { this.uri.set(uri); }
56
	public StringProperty UriProperty() { return uri; }
57
}
158
A src/main/java/com/scrivenvar/decorators/RVariableDecorator.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.decorators;
29
30
/**
31
 * Brackets variable names with <code>`r#</code> and <code>`</code>.
32
 *
33
 * @author White Magic Software, Ltd.
34
 */
35
public class RVariableDecorator implements VariableDecorator {
36
37
  /**
38
   * Returns the given string R-escaping backticks prepended and appended. This
39
   * is not null safe. Do not pass null into this method.
40
   *
41
   * @param variableName The string to decorate.
42
   *
43
   * @return "`r#" + variableName + "`".
44
   */
45
  @Override
46
  public String decorate( final String variableName ) {
47
    return "`r#" + variableName + "`";
48
  }
49
}
150
A src/main/java/com/scrivenvar/decorators/VariableDecorator.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.decorators;
29
30
/**
31
 * Responsible for updating variable names to use a machine-readable format
32
 * corresponding to the type of file being edited.
33
 */
34
public interface VariableDecorator {
35
36
  /**
37
   * This decorates a variable name based on some criteria determined by the
38
   * factory that creates implementations of this interface.
39
   *
40
   * @param variableName The text to decorate as per the filename extension
41
   * would indicate (e.g., ".md" goes to $VAR$ while ".Rmd" goes to `r#VAR`).
42
   *
43
   * @return The given variable name modified with its requisite delimiters.
44
   */
45
  public String decorate( String variableName );
46
}
147
A src/main/java/com/scrivenvar/decorators/YamlVariableDecorator.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.decorators;
29
30
/**
31
 * Brackets variable names with dollar symbols.
32
 *
33
 * @author White Magic Software, Ltd.
34
 */
35
public class YamlVariableDecorator implements VariableDecorator {
36
37
  /**
38
   * Matches variables delimited by dollar symbols. The outer group is necessary
39
   * for substring replacement of delimited references.
40
   */
41
  public final static String REGEX = "(\\$(.*?)\\$)";
42
43
  /**
44
   * Returns the given string with a $ symbol prepended and appended. This is
45
   * not null safe. Do not pass null into this method.
46
   *
47
   * @param variableName The string to decorate.
48
   *
49
   * @return "$" + variableName + "$".
50
   */
51
  @Override
52
  public String decorate( final String variableName ) {
53
    return "$" + variableName + "$";
54
  }
55
}
156
A src/main/java/com/scrivenvar/definition/AbstractDefinitionSource.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.definition;
29
30
/**
31
 * Implements common behaviour for definition sources.
32
 * 
33
 * @author White Magic Software, Ltd.
34
 */
35
public abstract class AbstractDefinitionSource implements DefinitionSource {
36
}
137
A src/main/java/com/scrivenvar/definition/DefinitionFactory.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.definition;
29
30
import com.scrivenvar.Services;
31
import com.scrivenvar.definition.yaml.YamlFileDefinitionSource;
32
import com.scrivenvar.predicates.files.FileTypePredicate;
33
import com.scrivenvar.service.Settings;
34
import java.nio.file.Path;
35
import java.util.Iterator;
36
import java.util.List;
37
38
/**
39
 * Responsible for creating objects that can read and write definition data
40
 * sources. The data source could be YAML, TOML, JSON, flat files, or from a
41
 * database.
42
 *
43
 * @author White Magic Software, Ltd.
44
 */
45
public class DefinitionFactory {
46
47
  /**
48
   * Refers to filename extension settings in the configuration file. Do not
49
   * terminate this key prefix with a period.
50
   */
51
  private static final String EXTENSIONS_PREFIX = "file.ext.definition";
52
53
  private final Settings settings = Services.load( Settings.class );
54
55
  /**
56
   * Default (empty) constructor.
57
   */
58
  public DefinitionFactory() {
59
  }
60
61
  /**
62
   * Creates a definition source that can read and write files that match the
63
   * given file type (from the path).
64
   *
65
   * @param path Reference to a variable definition file.
66
   *
67
   * @return
68
   */
69
  public DefinitionSource fileDefinitionSource( final Path path ) {
70
    final Settings properties = getSettings();
71
    final Iterator<String> keys = properties.getKeys( EXTENSIONS_PREFIX );
72
73
    DefinitionSource definitions = null;
74
75
    while( keys.hasNext() ) {
76
      final String key = keys.next();
77
      final List<String> patterns = properties.getStringSettingList( key );
78
      final FileTypePredicate predicate = new FileTypePredicate( patterns );
79
80
      if( predicate.test( path.toFile() ) ) {
81
        final String filetype = key.replace( EXTENSIONS_PREFIX + ".", "" );
82
83
        definitions = createFileDefinitionSource( filetype, path );
84
      }
85
    }
86
87
    return definitions;
88
  }
89
90
  /**
91
   * Creates a definition source based on the file type.
92
   *
93
   * @param filetype Property key name suffix from settings.properties file.
94
   * @param path Path to the file that corresponds to the extension.
95
   *
96
   * @return A DefinitionSource capable of parsing the data stored at the path.
97
   */
98
  private DefinitionSource createFileDefinitionSource(
99
    final String filetype, final Path path ) {
100
    final DefinitionSource result;
101
102
    switch( filetype ) {
103
      case "yaml":
104
        result = new YamlFileDefinitionSource( path );
105
        break;
106
107
      default:
108
        result = new EmptyDefinitionSource();
109
        break;
110
    }
111
112
    return result;
113
  }
114
115
  private Settings getSettings() {
116
    return this.settings;
117
  }
118
}
1119
A src/main/java/com/scrivenvar/definition/DefinitionPane.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.definition;
29
30
import com.scrivenvar.AbstractPane;
31
import static com.scrivenvar.definition.yaml.YamlParser.SEPARATOR;
32
import com.scrivenvar.predicates.strings.ContainsPredicate;
33
import com.scrivenvar.predicates.strings.StartsPredicate;
34
import com.scrivenvar.predicates.strings.StringPredicate;
35
import static com.scrivenvar.util.Lists.getFirst;
36
import java.util.List;
37
import javafx.collections.ObservableList;
38
import javafx.scene.Node;
39
import javafx.scene.control.MultipleSelectionModel;
40
import javafx.scene.control.SelectionMode;
41
import javafx.scene.control.TreeItem;
42
import javafx.scene.control.TreeView;
43
44
/**
45
 * Provides a list of variables that can be referenced in the editor.
46
 *
47
 * @author White Magic Software, Ltd.
48
 */
49
public class DefinitionPane extends AbstractPane {
50
51
  /**
52
   * Trimmed off the end of a word to match a variable name.
53
   */
54
  private final static String TERMINALS = ":;,.!?-/\\¡¿";
55
56
  private TreeView<String> treeView;
57
58
  /**
59
   * Constructs a definition pane with a given tree view root.
60
   *
61
   * @see YamlTreeAdapter.adapt
62
   * @param root The root of the variable definition tree.
63
   */
64
  public DefinitionPane( final TreeView<String> root ) {
65
    setTreeView( root );
66
    initTreeView();
67
  }
68
69
  public void clear() {
70
    getTreeView().setRoot( null );
71
  }
72
73
  /**
74
   * Finds a tree item with a value that exactly matches the given word.
75
   *
76
   * @param trunk The root item containing a list of nodes to search.
77
   * @param word The value of the item to find.
78
   * @param predicate Helps determine whether the node value matches the word.
79
   *
80
   * @return The item that matches the given word, or null if not found.
81
   */
82
  private TreeItem<String> findNode(
83
    final TreeItem<String> trunk,
84
    final StringPredicate predicate ) {
85
    final List<TreeItem<String>> branches = trunk.getChildren();
86
    TreeItem<String> result = null;
87
88
    for( final TreeItem<String> leaf : branches ) {
89
      if( predicate.test( leaf.getValue() ) ) {
90
        result = leaf;
91
        break;
92
      }
93
    }
94
95
    return result;
96
  }
97
98
  /**
99
   * Calls findNode with the EqualsPredicate.
100
   *
101
   * @see findNode( TreeItem, String, Predicate )
102
   * @return The result from findNode.
103
   */
104
  private TreeItem<String> findStartsNode(
105
    final TreeItem<String> trunk,
106
    final String word ) {
107
    return findNode( trunk, new StartsPredicate( word ) );
108
  }
109
110
  /**
111
   * Calls findNode with the ContainsPredicate.
112
   *
113
   * @see findNode( TreeItem, String, Predicate )
114
   * @return The result from findNode.
115
   */
116
  private TreeItem<String> findSubstringNode(
117
    final TreeItem<String> trunk,
118
    final String word ) {
119
    return findNode( trunk, new ContainsPredicate( word ) );
120
  }
121
122
  /**
123
   * Finds a node that matches a prefix and suffix specified by the given path
124
   * variable. The prefix must match a valid node value. The suffix refers to
125
   * the start of a string that matches zero or more children of the node
126
   * specified by the prefix. The algorithm has the following cases:
127
   *
128
   * <ol>
129
   * <li>Path is empty, return first child.</li>
130
   * <li>Path contains a complete match, return corresponding node.</li>
131
   * <li>Path contains a partial match, return nearest node.</li>
132
   * <li>Path contains a complete and partial match, return nearest node.</li>
133
   * </ol>
134
   *
135
   * @param word The word typed by the user, which contains dot-separated node
136
   * names that represent a path within the YAML tree plus a partial variable
137
   * name match (for a node).
138
   *
139
   * @return The node value that starts with the suffix portion of the given
140
   * path, never null.
141
   */
142
  public TreeItem<String> findNode( final String word ) {
143
    String path = word;
144
145
    TreeItem<String> cItem = getTreeRoot();
146
    TreeItem<String> pItem = cItem;
147
148
    int index = path.indexOf( SEPARATOR );
149
150
    while( index >= 0 ) {
151
      final String node = path.substring( 0, index );
152
      path = path.substring( index + 1 );
153
154
      if( (cItem = findStartsNode( cItem, node )) == null ) {
155
        break;
156
      }
157
158
      index = path.indexOf( SEPARATOR );
159
      pItem = cItem;
160
    }
161
162
    // Find the node that starts with whatever the user typed.
163
    cItem = findStartsNode( pItem, path );
164
165
    // If there was no matching node, then find a substring match.
166
    if( cItem == null ) {
167
      cItem = findSubstringNode( pItem, path );
168
    }
169
170
    // If neither starts with nor substring matched a node, revert to the last
171
    // known valid node.
172
    if( cItem == null ) {
173
      cItem = pItem;
174
    }
175
176
    return sanitize( cItem );
177
  }
178
179
  /**
180
   * Returns the leaf that matches the given value. If the value is terminally
181
   * punctuated, the punctuation is removed if no match was found.
182
   *
183
   * @param value The value to find, never null.
184
   *
185
   * @return The leaf that contains the given value, or null if neither the
186
   * original value nor the terminally-trimmed value was found.
187
   */
188
  public VariableTreeItem<String> findLeaf( final String value ) {
189
    final VariableTreeItem<String> root = getTreeRoot();
190
    final VariableTreeItem<String> leaf = root.findLeaf( value );
191
192
    return leaf == null
193
      ? root.findLeaf( rtrimTerminalPunctuation( value ) )
194
      : leaf;
195
  }
196
197
  /**
198
   * Removes punctuation from the end of a string. The character set includes:
199
   * <code>:;,.!?-/\¡¿</code>.
200
   *
201
   * @param s The string to trim, never null.
202
   *
203
   * @return The string trimmed of all terminal characters from the end
204
   */
205
  private String rtrimTerminalPunctuation( final String s ) {
206
    final StringBuilder result = new StringBuilder( s.trim() );
207
208
    while( TERMINALS.contains( "" + result.charAt( result.length() - 1 ) ) ) {
209
      result.setLength( result.length() - 1 );
210
    }
211
212
    return result.toString();
213
  }
214
215
  /**
216
   * Returns the tree root if either item or its first child are null.
217
   *
218
   * @param item The item to make null safe.
219
   *
220
   * @return A non-null TreeItem, possibly the root item (to avoid null).
221
   */
222
  private TreeItem<String> sanitize( final TreeItem<String> item ) {
223
    final TreeItem<String> result = item == getTreeRoot()
224
      ? getFirst( item.getChildren() )
225
      : item;
226
227
    return result == null ? item : result;
228
  }
229
230
  /**
231
   * Expands the node to the root, recursively.
232
   *
233
   * @param <T> The type of tree item to expand (usually String).
234
   * @param node The node to expand.
235
   */
236
  public <T> void expand( final TreeItem<T> node ) {
237
    if( node != null ) {
238
      expand( node.getParent() );
239
240
      if( !node.isLeaf() ) {
241
        node.setExpanded( true );
242
      }
243
    }
244
  }
245
246
  public void select( final TreeItem<String> item ) {
247
    clearSelection();
248
    selectItem( getTreeView().getRow( item ) );
249
  }
250
251
  private void clearSelection() {
252
    getSelectionModel().clearSelection();
253
  }
254
255
  private void selectItem( final int row ) {
256
    getSelectionModel().select( row );
257
  }
258
259
  /**
260
   * Collapses the tree, recursively.
261
   */
262
  public void collapse() {
263
    collapse( getTreeRoot().getChildren() );
264
  }
265
266
  /**
267
   * Collapses the tree, recursively.
268
   *
269
   * @param <T> The type of tree item to expand (usually String).
270
   * @param node The nodes to collapse.
271
   */
272
  private <T> void collapse( ObservableList<TreeItem<T>> nodes ) {
273
    for( final TreeItem<T> node : nodes ) {
274
      node.setExpanded( false );
275
      collapse( node.getChildren() );
276
    }
277
  }
278
279
  private void initTreeView() {
280
    getSelectionModel().setSelectionMode( SelectionMode.MULTIPLE );
281
  }
282
283
  /**
284
   * Returns the root node to the tree view.
285
   *
286
   * @return getTreeView()
287
   */
288
  public Node getNode() {
289
    return getTreeView();
290
  }
291
292
  private MultipleSelectionModel getSelectionModel() {
293
    return getTreeView().getSelectionModel();
294
  }
295
296
  /**
297
   * Returns the tree view that contains the YAML definition hierarchy.
298
   *
299
   * @return A non-null instance.
300
   */
301
  private TreeView<String> getTreeView() {
302
    return this.treeView;
303
  }
304
305
  /**
306
   * Returns the root of the tree.
307
   *
308
   * @return The first node added to the YAML definition tree.
309
   */
310
  private VariableTreeItem<String> getTreeRoot() {
311
    return (VariableTreeItem<String>)getTreeView().getRoot();
312
  }
313
314
  public <T> boolean isRoot( final TreeItem<T> item ) {
315
    return getTreeRoot().equals( item );
316
  }
317
318
  /**
319
   * Sets the tree view (called by the constructor).
320
   *
321
   * @param treeView
322
   */
323
  private void setTreeView( TreeView<String> treeView ) {
324
    if( treeView != null ) {
325
      this.treeView = treeView;
326
    }
327
  }
328
}
1329
A src/main/java/com/scrivenvar/definition/DefinitionSource.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.definition;
29
30
import java.io.IOException;
31
import java.util.Map;
32
import javafx.scene.control.TreeView;
33
34
/**
35
 * Represents behaviours for reading and writing variable definitions.
36
 *
37
 * @author White Magic Software, Ltd.
38
 */
39
public interface DefinitionSource {
40
41
  /**
42
   * Creates a TreeView from this definition source. The definition source is
43
   * responsible for observing the TreeView instance for changes and persisting
44
   * them, if needed.
45
   *
46
   * @return A hierarchical tree suitable for displaying in the definition pane.
47
   *
48
   * @throws IOException Could not obtain the definition source data.
49
   */
50
  public TreeView<String> asTreeView() throws IOException;
51
  
52
  /**
53
   * Returns all the strings with their values resolved in a flat hierarchy.
54
   * This copies all the keys and resolved values into a new map.
55
   *
56
   * @return The new map created with all values having been resolved,
57
   * recursively.
58
   */
59
  public Map<String, String> getResolvedMap();
60
}
161
A src/main/java/com/scrivenvar/definition/EmptyDefinitionSource.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.definition;
29
30
import java.io.IOException;
31
import java.util.HashMap;
32
import java.util.Map;
33
import javafx.scene.control.TreeView;
34
35
/**
36
 * Creates a definition source that has no information to load or save.
37
 *
38
 * @author White Magic Software, Ltd.
39
 */
40
public class EmptyDefinitionSource extends AbstractDefinitionSource {
41
42
  public EmptyDefinitionSource() {
43
  }
44
45
  @Override
46
  public TreeView<String> asTreeView() throws IOException {
47
    return new TreeView<>();
48
  }
49
50
  @Override
51
  public Map<String, String> getResolvedMap() {
52
    return new HashMap<>();
53
  }
54
55
}
156
A src/main/java/com/scrivenvar/definition/VariableTreeItem.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.definition;
29
30
import com.scrivenvar.decorators.VariableDecorator;
31
import com.scrivenvar.decorators.YamlVariableDecorator;
32
import static com.scrivenvar.definition.yaml.YamlParser.SEPARATOR;
33
import static com.scrivenvar.editors.VariableNameInjector.DEFAULT_MAX_VAR_LENGTH;
34
import java.util.HashMap;
35
import java.util.Map;
36
import java.util.Stack;
37
import javafx.scene.control.TreeItem;
38
39
/**
40
 * Provides behaviour afforded to variable names and their corresponding value.
41
 *
42
 * @author White Magic Software, Ltd.
43
 * @param <T> The type of TreeItem (usually String).
44
 */
45
public class VariableTreeItem<T> extends TreeItem<T> {
46
47
  private final static int DEFAULT_MAP_SIZE = 1000;
48
  
49
  private final static VariableDecorator VARIABLE_DECORATOR =
50
    new YamlVariableDecorator();
51
52
  /**
53
   * Flattened tree.
54
   */
55
  private Map<String, String> map;
56
57
  /**
58
   * Constructs a new item with a default value.
59
   *
60
   * @param value Passed up to superclass.
61
   */
62
  public VariableTreeItem( final T value ) {
63
    super( value );
64
  }
65
66
  /**
67
   * Finds a leaf starting at the current node with text that matches the given
68
   * value.
69
   *
70
   * @param text The text to match against each leaf in the tree.
71
   *
72
   * @return The leaf that has a value starting with the given text.
73
   */
74
  public VariableTreeItem<T> findLeaf( final String text ) {
75
    final Stack<VariableTreeItem<T>> stack = new Stack<>();
76
    final VariableTreeItem<T> root = this;
77
78
    stack.push( root );
79
80
    boolean found = false;
81
    VariableTreeItem<T> node = null;
82
83
    while( !found && !stack.isEmpty() ) {
84
      node = stack.pop();
85
86
      if( node.valueStartsWith( text ) ) {
87
        found = true;
88
      } else {
89
        for( final TreeItem<T> child : node.getChildren() ) {
90
          stack.push( (VariableTreeItem<T>)child );
91
        }
92
93
        // No match found, yet.
94
        node = null;
95
      }
96
    }
97
98
    return (VariableTreeItem<T>)node;
99
  }
100
101
  /**
102
   * Returns true if this node is a leaf and its value starts with the given
103
   * text.
104
   *
105
   * @param s The text to compare against the node value.
106
   *
107
   * @return true Node is a leaf and its value starts with the given value.
108
   */
109
  private boolean valueStartsWith( final String s ) {
110
    return isLeaf() && getValue().toString().startsWith( s );
111
  }
112
113
  /**
114
   * Returns the path for this node, with nodes made distinct using the
115
   * separator character. This uses two loops: one for pushing nodes onto a
116
   * stack and one for popping them off to create the path in desired order.
117
   *
118
   * @return A non-null string, possibly empty.
119
   */
120
  public String toPath() {
121
    final Stack<TreeItem<T>> stack = new Stack<>();
122
    TreeItem<T> node = this;
123
124
    while( node.getParent() != null ) {
125
      stack.push( node );
126
      node = node.getParent();
127
    }
128
129
    final StringBuilder sb = new StringBuilder( DEFAULT_MAX_VAR_LENGTH );
130
131
    while( !stack.isEmpty() ) {
132
      node = stack.pop();
133
134
      if( !node.isLeaf() ) {
135
        sb.append( node.getValue() );
136
137
        // This will add a superfluous separator, but instead of peeking at
138
        // the stack all the time, the last separator will be removed outside
139
        // the loop (one operation executed once).
140
        sb.append( SEPARATOR );
141
      }
142
    }
143
144
    // Remove the trailing SEPARATOR.
145
    if( sb.length() > 0 ) {
146
      sb.setLength( sb.length() - 1 );
147
    }
148
149
    return sb.toString();
150
  }
151
152
  /**
153
   * Returns the hierarchy, flattened to key-value pairs.
154
   *
155
   * @return A map of this tree's key-value pairs.
156
   */
157
  public Map<String, String> getMap() {
158
    if( this.map == null ) {
159
      this.map = new HashMap<>( DEFAULT_MAP_SIZE );
160
      populate( this, this.map );
161
    }
162
163
    return this.map;
164
  }
165
166
  private void populate( final TreeItem<T> parent, final Map<String, String> map ) {
167
    for( final TreeItem<T> child : parent.getChildren() ) {
168
      if( child.isLeaf() ) {
169
        @SuppressWarnings( "unchecked" )
170
        final String key = toVariable( ((VariableTreeItem<String>)child).toPath() );
171
        final String value = child.getValue().toString();
172
173
        map.put( key, value );
174
      } else {
175
        populate( child, map );
176
      }
177
    }
178
  }
179
180
  /**
181
   * Converts the name of the key to a simple variable by enclosing it with
182
   * dollar symbols.
183
   *
184
   * @param key The key name to change to a variable.
185
   *
186
   * @return $key$
187
   */
188
  public String toVariable( final String key ) {
189
    return VARIABLE_DECORATOR.decorate( key );
190
  }
191
}
1192
A src/main/java/com/scrivenvar/definition/yaml/FileDefinitionSource.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.definition.yaml;
29
30
import com.scrivenvar.definition.AbstractDefinitionSource;
31
import java.nio.file.Path;
32
33
/**
34
 * Implements common behaviour for file definition sources.
35
 *
36
 * @author White Magic Software, Ltd.
37
 */
38
public abstract class FileDefinitionSource extends AbstractDefinitionSource {
39
40
  private Path path;
41
42
  /**
43
   * Constructs a new file definition source that can read and write data in the
44
   * hierarchical format contained within the file location specified by the
45
   * path.
46
   *
47
   * @param path Must not be null.
48
   */
49
  public FileDefinitionSource( final Path path ) {
50
    setPath( path );
51
  }
52
53
  private void setPath( final Path path ) {
54
    this.path = path;
55
  }
56
57
  protected Path getPath() {
58
    return this.path;
59
  }
60
61
  /**
62
   * Returns the path represented by this object.
63
   *
64
   * @return The
65
   */
66
  @Override
67
  public String toString() {
68
    return getPath().toString();
69
  }
70
}
171
A src/main/java/com/scrivenvar/definition/yaml/YamlFileDefinitionSource.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.definition.yaml;
29
30
import static com.scrivenvar.Messages.get;
31
import java.io.IOException;
32
import java.io.InputStream;
33
import java.nio.file.Files;
34
import java.nio.file.Path;
35
import java.util.Map;
36
import javafx.scene.control.TreeView;
37
38
/**
39
 * Represents a definition data source for YAML files.
40
 *
41
 * @author White Magic Software, Ltd.
42
 */
43
public class YamlFileDefinitionSource extends FileDefinitionSource {
44
45
  private YamlTreeAdapter yamlTreeAdapter;
46
  private YamlParser yamlParser;
47
48
  /**
49
   * Constructs a new YAML definition source, populated from the given file.
50
   *
51
   * @param path Path to the YAML definition file.
52
   */
53
  public YamlFileDefinitionSource( final Path path ) {
54
    super( path );
55
  }
56
57
  /**
58
   * TODO: Associate variable file with path to current file.
59
   *
60
   * @return The TreeView for this definition source.
61
   *
62
   * @throws IOException
63
   */
64
  @Override
65
  public TreeView<String> asTreeView() throws IOException {
66
67
    try( final InputStream in = Files.newInputStream( getPath() ) ) {
68
      return getYamlTreeAdapter().adapt(
69
        in,
70
        get( "Pane.defintion.node.root.title" )
71
      );
72
    }
73
  }
74
75
  @Override
76
  public Map<String, String> getResolvedMap() {
77
    return getYamlParser().createResolvedMap();
78
  }
79
80
  private YamlTreeAdapter getYamlTreeAdapter() {
81
    if( this.yamlTreeAdapter == null ) {
82
      setYamlTreeAdapter( new YamlTreeAdapter( getYamlParser() ) );
83
    }
84
85
    return this.yamlTreeAdapter;
86
  }
87
88
  private void setYamlTreeAdapter( final YamlTreeAdapter yamlTreeAdapter ) {
89
    this.yamlTreeAdapter = yamlTreeAdapter;
90
  }
91
92
  private YamlParser getYamlParser() {
93
    if( this.yamlParser == null ) {
94
      setYamlParser( new YamlParser() );
95
    }
96
97
    return this.yamlParser;
98
  }
99
100
  private void setYamlParser( final YamlParser yamlParser ) {
101
    this.yamlParser = yamlParser;
102
  }
103
104
  private InputStream asStream( final String resource ) {
105
    return getClass().getResourceAsStream( resource );
106
  }
107
}
1108
A src/main/java/com/scrivenvar/definition/yaml/YamlParser.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.definition.yaml;
29
30
import com.fasterxml.jackson.core.JsonGenerationException;
31
import com.fasterxml.jackson.core.ObjectCodec;
32
import com.fasterxml.jackson.core.io.IOContext;
33
import com.fasterxml.jackson.databind.JsonNode;
34
import com.fasterxml.jackson.databind.ObjectMapper;
35
import com.fasterxml.jackson.databind.node.ObjectNode;
36
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
37
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
38
import com.scrivenvar.decorators.VariableDecorator;
39
import com.scrivenvar.decorators.YamlVariableDecorator;
40
import java.io.IOException;
41
import java.io.InputStream;
42
import java.io.Writer;
43
import java.security.InvalidParameterException;
44
import java.text.MessageFormat;
45
import java.util.HashMap;
46
import java.util.Map;
47
import java.util.Map.Entry;
48
import java.util.regex.Matcher;
49
import java.util.regex.Pattern;
50
import org.yaml.snakeyaml.DumperOptions;
51
52
/**
53
 * <p>
54
 * This program loads a YAML document into memory, scans for variable
55
 * declarations, then substitutes any self-referential values back into the
56
 * document. Its output is the given YAML document without any variables.
57
 * Variables in the YAML document are denoted using a bracketed dollar symbol
58
 * syntax. For example: $field.name$. Some nomenclature to keep from going
59
 * squirrely, consider:
60
 * </p>
61
 *
62
 * <pre>
63
 *   root:
64
 *     node:
65
 *       name: $field.name$
66
 *   field:
67
 *     name: Alan Turing
68
 * </pre>
69
 *
70
 * The various components of the given YAML are called:
71
 *
72
 * <ul>
73
 * <li><code>$field.name$</code> - delimited reference</li>
74
 * <li><code>field.name</code> - reference</li>
75
 * <li><code>name</code> - YAML field</li>
76
 * <li><code>Alan Turing</code> - (dereferenced) field value</li>
77
 * </ul>
78
 *
79
 * @author White Magic Software, Ltd.
80
 */
81
public class YamlParser {
82
83
  /**
84
   * Separates YAML variable nodes (e.g., the dots in
85
   * <code>$root.node.var$</code>).
86
   */
87
  public static final String SEPARATOR = ".";
88
89
  private final static int GROUP_DELIMITED = 1;
90
  private final static int GROUP_REFERENCE = 2;
91
92
  private final static VariableDecorator VARIABLE_DECORATOR
93
    = new YamlVariableDecorator();
94
95
  /**
96
   * Compiled version of DEFAULT_REGEX.
97
   */
98
  private final static Pattern REGEX_PATTERN
99
    = Pattern.compile( YamlVariableDecorator.REGEX );
100
101
  /**
102
   * Should be JsonPointer.SEPARATOR, but Jackson YAML uses magic values.
103
   */
104
  private final static char SEPARATOR_YAML = '/';
105
106
  /**
107
   * Start of the Universe (the YAML document node that contains all others).
108
   */
109
  private ObjectNode documentRoot;
110
111
  /**
112
   * Map of references to dereferenced field values.
113
   */
114
  private Map<String, String> references;
115
116
  public YamlParser() {
117
  }
118
119
  /**
120
   * Returns the given string with all the delimited references swapped with
121
   * their recursively resolved values.
122
   *
123
   * @param text The text to parse with zero or more delimited references to
124
   * replace.
125
   *
126
   * @return The substituted value.
127
   */
128
  public String substitute( String text ) {
129
    final Matcher matcher = patternMatch( text );
130
    final Map<String, String> map = getReferences();
131
132
    while( matcher.find() ) {
133
      final String key = matcher.group( GROUP_DELIMITED );
134
      final String value = map.get( key );
135
136
      if( value == null ) {
137
        missing( text );
138
      } else {
139
        text = text.replace( key, value );
140
      }
141
    }
142
143
    return text;
144
  }
145
146
  /**
147
   * Returns all the strings with their values resolved in a flat hierarchy.
148
   * This copies all the keys and resolved values into a new map.
149
   *
150
   * @return The new map created with all values having been resolved,
151
   * recursively.
152
   */
153
  public Map<String, String> createResolvedMap() {
154
    final Map<String, String> map = new HashMap<>( 1024 );
155
156
    resolve( getDocumentRoot(), "", map );
157
158
    return map;
159
  }
160
161
  /**
162
   * Iterate over a given root node (at any level of the tree) and adapt each
163
   * leaf node.
164
   *
165
   * @param rootNode A JSON node (YAML node) to adapt.
166
   */
167
  private void resolve(
168
    final JsonNode rootNode, final String path, final Map<String, String> map ) {
169
170
    rootNode.fields().forEachRemaining(
171
      (Entry<String, JsonNode> leaf) -> resolve( leaf, path, map )
172
    );
173
  }
174
175
  /**
176
   * Recursively adapt each rootNode to a corresponding rootItem.
177
   *
178
   * @param rootNode The node to adapt.
179
   */
180
  private void resolve(
181
    final Entry<String, JsonNode> rootNode,
182
    final String path,
183
    final Map<String, String> map ) {
184
185
    final JsonNode leafNode = rootNode.getValue();
186
    final String key = rootNode.getKey();
187
188
    if( leafNode.isValueNode() ) {
189
      final String value = rootNode.getValue().asText();
190
191
      map.put( VARIABLE_DECORATOR.decorate( path + key ), substitute( value ) );
192
    }
193
194
    if( leafNode.isObject() ) {
195
      resolve( leafNode, path + key + SEPARATOR, map );
196
    }
197
  }
198
199
  /**
200
   * Reads the first document from the given stream of YAML data and returns a
201
   * corresponding object that represents the YAML hierarchy. The calling class
202
   * is responsible for closing the stream. Calling classes should use
203
   * <code>JsonNode.fields()</code> to walk through the YAML tree of fields.
204
   *
205
   * @param in The input stream containing YAML content.
206
   *
207
   * @return An object hierarchy to represent the content.
208
   *
209
   * @throws IOException Could not read the stream.
210
   */
211
  public JsonNode process( final InputStream in ) throws IOException {
212
213
    final ObjectNode root = (ObjectNode)getObjectMapper().readTree( in );
214
    setDocumentRoot( root );
215
    process( root );
216
    return getDocumentRoot();
217
  }
218
219
  /**
220
   * Iterate over a given root node (at any level of the tree) and process each
221
   * leaf node.
222
   *
223
   * @param root A node to process.
224
   */
225
  private void process( final JsonNode root ) {
226
    root.fields().forEachRemaining( this::process );
227
  }
228
229
  /**
230
   * Process the given field, which is a named node. This is where the
231
   * application does the up-front work of mapping references to their fully
232
   * recursively dereferenced values.
233
   *
234
   * @param field The named node.
235
   */
236
  private void process( final Entry<String, JsonNode> field ) {
237
    final JsonNode node = field.getValue();
238
239
    if( node.isObject() ) {
240
      process( node );
241
    } else {
242
      final JsonNode fieldValue = field.getValue();
243
244
      // Only basic data types can be parsed into variable values. For
245
      // node structures, YAML has a built-in mechanism.
246
      if( fieldValue.isValueNode() ) {
247
        try {
248
          resolve( fieldValue.asText() );
249
        } catch( StackOverflowError e ) {
250
          throw new IllegalArgumentException(
251
            "Unresolvable: " + node.textValue() + " = " + fieldValue );
252
        }
253
      }
254
    }
255
  }
256
257
  /**
258
   * Inserts the delimited references and field values into the cache. This will
259
   * overwrite existing references.
260
   *
261
   * @param fieldValue YAML field containing zero or more delimited references.
262
   * If it contains a delimited reference, the parameter is modified with the
263
   * dereferenced value before it is returned.
264
   *
265
   * @return fieldValue without delimited references.
266
   */
267
  private String resolve( String fieldValue ) {
268
    final Matcher matcher = patternMatch( fieldValue );
269
270
    while( matcher.find() ) {
271
      final String delimited = matcher.group( GROUP_DELIMITED );
272
      final String reference = matcher.group( GROUP_REFERENCE );
273
      final String dereference = resolve( lookup( reference ) );
274
275
      fieldValue = fieldValue.replace( delimited, dereference );
276
277
      // This will perform some superfluous calls by overwriting existing
278
      // items in the delimited reference map.
279
      put( delimited, dereference );
280
    }
281
282
    return fieldValue;
283
  }
284
285
  /**
286
   * Inserts a key/value pair into the references map. The map retains
287
   * references and dereferenced values found in the YAML. If the reference
288
   * already exists, this will overwrite with a new value.
289
   *
290
   * @param delimited The variable name.
291
   * @param dereferenced The resolved value.
292
   */
293
  private void put( String delimited, String dereferenced ) {
294
    if( dereferenced.isEmpty() ) {
295
      missing( delimited );
296
    } else {
297
      getReferences().put( delimited, dereferenced );
298
    }
299
  }
300
301
  /**
302
   * Writes the modified YAML document to standard output.
303
   */
304
  private void writeDocument() throws IOException {
305
    getObjectMapper().writeValue( System.out, getDocumentRoot() );
306
  }
307
308
  /**
309
   * Called when a delimited reference is dereferenced to an empty string. This
310
   * should produce a warning for the user.
311
   *
312
   * @param delimited Delimited reference with no derived value.
313
   */
314
  private void missing( final String delimited ) {
315
    throw new InvalidParameterException(
316
      MessageFormat.format( "Missing value for '{0}'.", delimited ) );
317
  }
318
319
  /**
320
   * Returns a REGEX_PATTERN matcher for the given text.
321
   *
322
   * @param text The text that contains zero or more instances of a
323
   * REGEX_PATTERN that can be found using the regular expression.
324
   */
325
  private Matcher patternMatch( String text ) {
326
    return getPattern().matcher( text );
327
  }
328
329
  /**
330
   * Finds the YAML value for a reference.
331
   *
332
   * @param reference References a value in the YAML document.
333
   *
334
   * @return The dereferenced value.
335
   */
336
  private String lookup( final String reference ) {
337
    return getDocumentRoot().at( asPath( reference ) ).asText();
338
  }
339
340
  /**
341
   * Converts a reference (not delimited) to a path that can be used to find a
342
   * value that should exist inside the YAML document.
343
   *
344
   * @param reference The reference to convert to a YAML document path.
345
   *
346
   * @return The reference with a leading slash and its separator characters
347
   * converted to slashes.
348
   */
349
  private String asPath( final String reference ) {
350
    return SEPARATOR_YAML + reference.replace( getDelimitedSeparator(), SEPARATOR_YAML );
351
  }
352
353
  /**
354
   * Sets the parent node for the entire YAML document tree.
355
   *
356
   * @param documentRoot The parent node.
357
   */
358
  private void setDocumentRoot( ObjectNode documentRoot ) {
359
    this.documentRoot = documentRoot;
360
  }
361
362
  /**
363
   * Returns the parent node for the entire YAML document tree.
364
   *
365
   * @return The parent node.
366
   */
367
  private ObjectNode getDocumentRoot() {
368
    return this.documentRoot;
369
  }
370
371
  /**
372
   * Returns the compiled regular expression REGEX_PATTERN used to match
373
   * delimited references.
374
   *
375
   * @return A compiled regex for use with the Matcher.
376
   */
377
  private Pattern getPattern() {
378
    return REGEX_PATTERN;
379
  }
380
381
  /**
382
   * Returns the list of references mapped to dereferenced values.
383
   *
384
   * @return
385
   */
386
  private Map<String, String> getReferences() {
387
    if( this.references == null ) {
388
      this.references = createReferences();
389
    }
390
391
    return this.references;
392
  }
393
394
  /**
395
   * Subclasses can override this method to insert their own map.
396
   *
397
   * @return An empty HashMap, never null.
398
   */
399
  protected Map<String, String> createReferences() {
400
    return new HashMap<>();
401
  }
402
403
  private class ResolverYAMLFactory extends YAMLFactory {
404
405
    @Override
406
    protected YAMLGenerator _createGenerator(
407
      final Writer out, final IOContext ctxt ) throws IOException {
408
409
      return new ResolverYAMLGenerator(
410
        ctxt, _generatorFeatures, _yamlGeneratorFeatures, _objectCodec,
411
        out, _version );
412
    }
413
  }
414
415
  private class ResolverYAMLGenerator extends YAMLGenerator {
416
417
    public ResolverYAMLGenerator(
418
      final IOContext ctxt,
419
      final int jsonFeatures,
420
      final int yamlFeatures,
421
      final ObjectCodec codec,
422
      final Writer out,
423
      final DumperOptions.Version version ) throws IOException {
424
425
      super( ctxt, jsonFeatures, yamlFeatures, codec, out, version );
426
    }
427
428
    @Override
429
    public void writeString( final String text )
430
      throws IOException, JsonGenerationException {
431
      super.writeString( substitute( text ) );
432
    }
433
  }
434
435
  private YAMLFactory getYAMLFactory() {
436
    return new ResolverYAMLFactory();
437
  }
438
439
  private ObjectMapper getObjectMapper() {
440
    return new ObjectMapper( getYAMLFactory() );
441
  }
442
443
  /**
444
   * Returns the character used to separate YAML paths within delimited
445
   * references. This will return only the first character of the command line
446
   * parameter, if the default is overridden.
447
   *
448
   * @return A period by default.
449
   */
450
  private char getDelimitedSeparator() {
451
    return SEPARATOR.charAt( 0 );
452
  }
453
}
1454
A src/main/java/com/scrivenvar/definition/yaml/YamlTreeAdapter.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.definition.yaml;
29
30
import com.fasterxml.jackson.databind.JsonNode;
31
import com.scrivenvar.definition.VariableTreeItem;
32
import java.io.IOException;
33
import java.io.InputStream;
34
import java.util.Map.Entry;
35
import javafx.scene.control.TreeItem;
36
import javafx.scene.control.TreeView;
37
38
/**
39
 * Transforms a JsonNode hierarchy into a tree that can be displayed in a user
40
 * interface.
41
 *
42
 * @author White Magic Software, Ltd.
43
 */
44
public class YamlTreeAdapter {
45
46
  private YamlParser yamlParser;
47
48
  public YamlTreeAdapter( final YamlParser parser ) {
49
    setYamlParser( parser );
50
  }
51
52
  /**
53
   * Converts a YAML document to a TreeView based on the document keys. Only the
54
   * first document in the stream is adapted. This does not close the stream.
55
   *
56
   * @param in Contains a YAML document.
57
   * @param name Root TreeItem node name.
58
   *
59
   * @return A TreeView populated with all the keys in the YAML document.
60
   *
61
   * @throws IOException Could not read from the stream.
62
   */
63
  public TreeView<String> adapt( final InputStream in, final String name )
64
    throws IOException {
65
66
    final JsonNode rootNode = getYamlParser().process( in );
67
    final TreeItem<String> rootItem = createTreeItem( name );
68
69
    rootItem.setExpanded( true );
70
    adapt( rootNode, rootItem );
71
    return new TreeView<>( rootItem );
72
  }
73
74
  /**
75
   * Iterate over a given root node (at any level of the tree) and adapt each
76
   * leaf node.
77
   *
78
   * @param rootNode A JSON node (YAML node) to adapt.
79
   * @param rootItem The tree item to use as the root when processing the node.
80
   */
81
  private void adapt(
82
    final JsonNode rootNode, final TreeItem<String> rootItem ) {
83
84
    rootNode.fields().forEachRemaining(
85
      (Entry<String, JsonNode> leaf) -> adapt( leaf, rootItem )
86
    );
87
  }
88
89
  /**
90
   * Recursively adapt each rootNode to a corresponding rootItem.
91
   *
92
   * @param rootNode The node to adapt.
93
   * @param rootItem The item to adapt using the node's key.
94
   */
95
  private void adapt(
96
    final Entry<String, JsonNode> rootNode, final TreeItem<String> rootItem ) {
97
98
    final JsonNode leafNode = rootNode.getValue();
99
    final String key = rootNode.getKey();
100
    final TreeItem<String> leaf = createTreeItem( key );
101
102
    if( leafNode.isValueNode() ) {
103
      leaf.getChildren().add( createTreeItem( rootNode.getValue().asText() ) );
104
    }
105
106
    rootItem.getChildren().add( leaf );
107
108
    if( leafNode.isObject() ) {
109
      adapt( leafNode, leaf );
110
    }
111
  }
112
113
  /**
114
   * Creates a new tree item that can be added to the tree view.
115
   *
116
   * @param value The node's value.
117
   *
118
   * @return A new tree item node, never null.
119
   */
120
  private TreeItem<String> createTreeItem( final String value ) {
121
    return new VariableTreeItem<>( value );
122
  }
123
124
  private YamlParser getYamlParser() {
125
    return this.yamlParser;
126
  }
127
128
  private void setYamlParser( final YamlParser yamlParser ) {
129
    this.yamlParser = yamlParser;
130
  }
131
}
1132
A src/main/java/com/scrivenvar/dialogs/ImageDialog.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
package com.scrivenvar.dialogs;
28
29
import com.scrivenvar.Messages;
30
import com.scrivenvar.controls.BrowseFileButton;
31
import com.scrivenvar.controls.EscapeTextField;
32
import com.scrivenvar.service.events.impl.ButtonOrderPane;
33
import java.nio.file.Path;
34
import javafx.application.Platform;
35
import javafx.beans.binding.Bindings;
36
import javafx.beans.property.SimpleStringProperty;
37
import javafx.beans.property.StringProperty;
38
import javafx.scene.control.ButtonBar.ButtonData;
39
import javafx.scene.control.ButtonType;
40
import javafx.scene.control.Dialog;
41
import javafx.scene.control.DialogPane;
42
import javafx.scene.control.Label;
43
import javafx.stage.FileChooser.ExtensionFilter;
44
import javafx.stage.Window;
45
import org.tbee.javafx.scene.layout.fxml.MigPane;
46
47
/**
48
 * Dialog to enter a markdown image.
49
 *
50
 * @author Karl Tauber
51
 */
52
public class ImageDialog extends Dialog<String> {
53
54
  private final StringProperty image = new SimpleStringProperty();
55
56
  public ImageDialog( Window owner, Path basePath ) {
57
    setTitle( Messages.get( "ImageDialog.title" ) );
58
    initOwner( owner );
59
    setResizable( true );
60
61
    initComponents();
62
63
    linkBrowseFileButton.setBasePath( basePath );
64
    linkBrowseFileButton.addExtensionFilter( new ExtensionFilter( Messages.get( "ImageDialog.chooser.imagesFilter" ), "*.png", "*.gif", "*.jpg" ) );
65
    linkBrowseFileButton.urlProperty().bindBidirectional( urlField.escapedTextProperty() );
66
67
    setDialogPane( new ButtonOrderPane() );
68
    final DialogPane dialogPane = getDialogPane();
69
    dialogPane.setContent( pane );
70
    dialogPane.getButtonTypes().addAll( ButtonType.OK, ButtonType.CANCEL );
71
72
    dialogPane.lookupButton( ButtonType.OK ).disableProperty().bind(
73
      urlField.escapedTextProperty().isEmpty()
74
      .or( textField.escapedTextProperty().isEmpty() ) );
75
76
    image.bind( Bindings.when( titleField.escapedTextProperty().isNotEmpty() )
77
      .then( Bindings.format( "![%s](%s \"%s\")", textField.escapedTextProperty(), urlField.escapedTextProperty(), titleField.escapedTextProperty() ) )
78
      .otherwise( Bindings.format( "![%s](%s)", textField.escapedTextProperty(), urlField.escapedTextProperty() ) ) );
79
    previewField.textProperty().bind( image );
80
81
    setResultConverter( dialogButton -> {
82
      ButtonData data = (dialogButton != null) ? dialogButton.getButtonData() : null;
83
      return (data == ButtonData.OK_DONE) ? image.get() : null;
84
    } );
85
86
    Platform.runLater( () -> {
87
      urlField.requestFocus();
88
89
      if( urlField.getText().startsWith( "http://" ) ) {
90
        urlField.selectRange( "http://".length(), urlField.getLength() );
91
      }
92
    } );
93
  }
94
95
  private void initComponents() {
96
    // JFormDesigner - Component initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents
97
    pane = new MigPane();
98
    Label urlLabel = new Label();
99
    urlField = new EscapeTextField();
100
    linkBrowseFileButton = new BrowseFileButton();
101
    Label textLabel = new Label();
102
    textField = new EscapeTextField();
103
    Label titleLabel = new Label();
104
    titleField = new EscapeTextField();
105
    Label previewLabel = new Label();
106
    previewField = new Label();
107
108
    //======== pane ========
109
    {
110
      pane.setCols( "[shrink 0,fill][300,grow,fill][fill]" );
111
      pane.setRows( "[][][][]" );
112
113
      //---- urlLabel ----
114
      urlLabel.setText( Messages.get( "ImageDialog.urlLabel.text" ) );
115
      pane.add( urlLabel, "cell 0 0" );
116
117
      //---- urlField ----
118
      urlField.setEscapeCharacters( "()" );
119
      urlField.setText( "http://yourlink.com" );
120
      urlField.setPromptText( "http://yourlink.com" );
121
      pane.add( urlField, "cell 1 0" );
122
      pane.add( linkBrowseFileButton, "cell 2 0" );
123
124
      //---- textLabel ----
125
      textLabel.setText( Messages.get( "ImageDialog.textLabel.text" ) );
126
      pane.add( textLabel, "cell 0 1" );
127
128
      //---- textField ----
129
      textField.setEscapeCharacters( "[]" );
130
      pane.add( textField, "cell 1 1 2 1" );
131
132
      //---- titleLabel ----
133
      titleLabel.setText( Messages.get( "ImageDialog.titleLabel.text" ) );
134
      pane.add( titleLabel, "cell 0 2" );
135
      pane.add( titleField, "cell 1 2 2 1" );
136
137
      //---- previewLabel ----
138
      previewLabel.setText( Messages.get( "ImageDialog.previewLabel.text" ) );
139
      pane.add( previewLabel, "cell 0 3" );
140
      pane.add( previewField, "cell 1 3 2 1" );
141
    }
142
    // JFormDesigner - End of component initialization  //GEN-END:initComponents
143
  }
144
145
  // JFormDesigner - Variables declaration - DO NOT MODIFY  //GEN-BEGIN:variables
146
  private MigPane pane;
147
  private EscapeTextField urlField;
148
  private BrowseFileButton linkBrowseFileButton;
149
  private EscapeTextField textField;
150
  private EscapeTextField titleField;
151
  private Label previewField;
152
	// JFormDesigner - End of variables declaration  //GEN-END:variables
153
}
1154
A src/main/java/com/scrivenvar/dialogs/ImageDialog.jfd
1
JFDML JFormDesigner: "9.9.9.9.9999" Java: "1.8.0_66" encoding: "UTF-8"
2
3
new FormModel {
4
	"i18n.bundlePackage": "com.scrivendor"
5
	"i18n.bundleName": "messages"
6
	"i18n.autoExternalize": true
7
	"i18n.keyPrefix": "ImageDialog"
8
	contentType: "form/javafx"
9
	root: new FormRoot {
10
		add( new FormContainer( "org.tbee.javafx.scene.layout.fxml.MigPane", new FormLayoutManager( class org.tbee.javafx.scene.layout.fxml.MigPane ) {
11
			"$layoutConstraints": ""
12
			"$columnConstraints": "[shrink 0,fill][300,grow,fill][fill]"
13
			"$rowConstraints": "[][][][]"
14
		} ) {
15
			name: "pane"
16
			add( new FormComponent( "javafx.scene.control.Label" ) {
17
				name: "urlLabel"
18
				"text": new FormMessage( null, "ImageDialog.urlLabel.text" )
19
				auxiliary() {
20
					"JavaCodeGenerator.variableLocal": true
21
				}
22
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
23
				"value": "cell 0 0"
24
			} )
25
			add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) {
26
				name: "urlField"
27
				"escapeCharacters": "()"
28
				"text": "http://yourlink.com"
29
				"promptText": "http://yourlink.com"
30
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
31
				"value": "cell 1 0"
32
			} )
33
			add( new FormComponent( "com.scrivendor.controls.BrowseFileButton" ) {
34
				name: "linkBrowseFileButton"
35
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
36
				"value": "cell 2 0"
37
			} )
38
			add( new FormComponent( "javafx.scene.control.Label" ) {
39
				name: "textLabel"
40
				"text": new FormMessage( null, "ImageDialog.textLabel.text" )
41
				auxiliary() {
42
					"JavaCodeGenerator.variableLocal": true
43
				}
44
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
45
				"value": "cell 0 1"
46
			} )
47
			add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) {
48
				name: "textField"
49
				"escapeCharacters": "[]"
50
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
51
				"value": "cell 1 1 2 1"
52
			} )
53
			add( new FormComponent( "javafx.scene.control.Label" ) {
54
				name: "titleLabel"
55
				"text": new FormMessage( null, "ImageDialog.titleLabel.text" )
56
				auxiliary() {
57
					"JavaCodeGenerator.variableLocal": true
58
				}
59
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
60
				"value": "cell 0 2"
61
			} )
62
			add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) {
63
				name: "titleField"
64
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
65
				"value": "cell 1 2 2 1"
66
			} )
67
			add( new FormComponent( "javafx.scene.control.Label" ) {
68
				name: "previewLabel"
69
				"text": new FormMessage( null, "ImageDialog.previewLabel.text" )
70
				auxiliary() {
71
					"JavaCodeGenerator.variableLocal": true
72
				}
73
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
74
				"value": "cell 0 3"
75
			} )
76
			add( new FormComponent( "javafx.scene.control.Label" ) {
77
				name: "previewField"
78
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
79
				"value": "cell 1 3 2 1"
80
			} )
81
		}, new FormLayoutConstraints( null ) {
82
			"location": new javafx.geometry.Point2D( 0.0, 0.0 )
83
			"size": new javafx.geometry.Dimension2D( 500.0, 300.0 )
84
		} )
85
	}
86
}
187
A src/main/java/com/scrivenvar/dialogs/LinkDialog.java
1
/*
2
 * Copyright 2016 Karl Tauber and White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.dialogs;
29
30
import com.scrivenvar.Messages;
31
import com.scrivenvar.controls.EscapeTextField;
32
import com.scrivenvar.editors.markdown.HyperlinkModel;
33
import com.scrivenvar.service.events.impl.ButtonOrderPane;
34
import java.nio.file.Path;
35
import javafx.application.Platform;
36
import javafx.beans.binding.Bindings;
37
import javafx.beans.property.SimpleStringProperty;
38
import javafx.beans.property.StringProperty;
39
import javafx.scene.control.ButtonBar.ButtonData;
40
import javafx.scene.control.ButtonType;
41
import javafx.scene.control.Dialog;
42
import javafx.scene.control.DialogPane;
43
import javafx.scene.control.Label;
44
import javafx.stage.Window;
45
import org.tbee.javafx.scene.layout.fxml.MigPane;
46
47
/**
48
 * Dialog to enter a markdown link.
49
 *
50
 * @author Karl Tauber
51
 */
52
public class LinkDialog extends Dialog<String> {
53
54
  private final StringProperty link = new SimpleStringProperty();
55
56
  public LinkDialog( final Window owner, final HyperlinkModel hyperlink, final Path basePath ) {
57
    setTitle( Messages.get( "LinkDialog.title" ) );
58
    initOwner( owner );
59
    setResizable( true );
60
61
    initComponents();
62
63
    setDialogPane( new ButtonOrderPane() );
64
65
    final DialogPane dialog = getDialogPane();
66
    dialog.setContent( pane );
67
    dialog.getButtonTypes().addAll( ButtonType.OK, ButtonType.CANCEL );
68
69
    dialog.lookupButton( ButtonType.OK ).disableProperty().bind(
70
      urlField.escapedTextProperty().isEmpty() );
71
72
    textField.setText( hyperlink.getText() );
73
    urlField.setText( hyperlink.getUrl() );
74
    titleField.setText( hyperlink.getTitle() );
75
76
    link.bind( Bindings.when( titleField.escapedTextProperty().isNotEmpty() )
77
      .then( Bindings.format( "[%s](%s \"%s\")", textField.escapedTextProperty(), urlField.escapedTextProperty(), titleField.escapedTextProperty() ) )
78
      .otherwise( Bindings.when( textField.escapedTextProperty().isNotEmpty() )
79
        .then( Bindings.format( "[%s](%s)", textField.escapedTextProperty(), urlField.escapedTextProperty() ) )
80
        .otherwise( urlField.escapedTextProperty() ) ) );
81
82
    setResultConverter( dialogButton -> {
83
      ButtonData data = (dialogButton != null) ? dialogButton.getButtonData() : null;
84
      return (data == ButtonData.OK_DONE) ? link.get() : null;
85
    } );
86
87
    Platform.runLater( () -> {
88
      urlField.requestFocus();
89
      urlField.selectRange( 0, urlField.getLength() );
90
    } );
91
  }
92
93
  private void initComponents() {
94
    // JFormDesigner - Component initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents
95
    pane = new MigPane();
96
    Label urlLabel = new Label();
97
    urlField = new EscapeTextField();
98
    Label textLabel = new Label();
99
    textField = new EscapeTextField();
100
    Label titleLabel = new Label();
101
    titleField = new EscapeTextField();
102
103
    //======== pane ========
104
    {
105
      pane.setCols( "[shrink 0,fill][300,grow,fill][fill][fill]" );
106
      pane.setRows( "[][][][]" );
107
108
      //---- urlLabel ----
109
      urlLabel.setText( Messages.get( "LinkDialog.urlLabel.text" ) );
110
      pane.add( urlLabel, "cell 0 0" );
111
112
      //---- urlField ----
113
      urlField.setEscapeCharacters( "()" );
114
      pane.add( urlField, "cell 1 0" );
115
116
      //---- textLabel ----
117
      textLabel.setText( Messages.get( "LinkDialog.textLabel.text" ) );
118
      pane.add( textLabel, "cell 0 1" );
119
120
      //---- textField ----
121
      textField.setEscapeCharacters( "[]" );
122
      pane.add( textField, "cell 1 1 3 1" );
123
124
      //---- titleLabel ----
125
      titleLabel.setText( Messages.get( "LinkDialog.titleLabel.text" ) );
126
      pane.add( titleLabel, "cell 0 2" );
127
      pane.add( titleField, "cell 1 2 3 1" );
128
    }
129
    // JFormDesigner - End of component initialization  //GEN-END:initComponents
130
  }
131
132
  // JFormDesigner - Variables declaration - DO NOT MODIFY  //GEN-BEGIN:variables
133
  private MigPane pane;
134
  private EscapeTextField urlField;
135
  private EscapeTextField textField;
136
  private EscapeTextField titleField;
137
  // JFormDesigner - End of variables declaration  //GEN-END:variables
138
}
1139
A src/main/java/com/scrivenvar/dialogs/LinkDialog.jfd
1
JFDML JFormDesigner: "9.9.9.9.9999" Java: "1.8.0_66" encoding: "UTF-8"
2
3
new FormModel {
4
	"i18n.bundlePackage": "com.scrivendor"
5
	"i18n.bundleName": "messages"
6
	"i18n.autoExternalize": true
7
	"i18n.keyPrefix": "LinkDialog"
8
	contentType: "form/javafx"
9
	root: new FormRoot {
10
		add( new FormContainer( "org.tbee.javafx.scene.layout.fxml.MigPane", new FormLayoutManager( class org.tbee.javafx.scene.layout.fxml.MigPane ) {
11
			"$layoutConstraints": ""
12
			"$columnConstraints": "[shrink 0,fill][300,grow,fill][fill][fill]"
13
			"$rowConstraints": "[][][][]"
14
		} ) {
15
			name: "pane"
16
			add( new FormComponent( "javafx.scene.control.Label" ) {
17
				name: "urlLabel"
18
				"text": new FormMessage( null, "LinkDialog.urlLabel.text" )
19
				auxiliary() {
20
					"JavaCodeGenerator.variableLocal": true
21
				}
22
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
23
				"value": "cell 0 0"
24
			} )
25
			add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) {
26
				name: "urlField"
27
				"escapeCharacters": "()"
28
				"text": "http://yourlink.com"
29
				"promptText": "http://yourlink.com"
30
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
31
				"value": "cell 1 0"
32
			} )
33
			add( new FormComponent( "com.scrivendor.controls.BrowseDirectoryButton" ) {
34
				name: "linkBrowseDirectoyButton"
35
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
36
				"value": "cell 2 0"
37
			} )
38
			add( new FormComponent( "com.scrivendor.controls.BrowseFileButton" ) {
39
				name: "linkBrowseFileButton"
40
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
41
				"value": "cell 3 0"
42
			} )
43
			add( new FormComponent( "javafx.scene.control.Label" ) {
44
				name: "textLabel"
45
				"text": new FormMessage( null, "LinkDialog.textLabel.text" )
46
				auxiliary() {
47
					"JavaCodeGenerator.variableLocal": true
48
				}
49
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
50
				"value": "cell 0 1"
51
			} )
52
			add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) {
53
				name: "textField"
54
				"escapeCharacters": "[]"
55
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
56
				"value": "cell 1 1 3 1"
57
			} )
58
			add( new FormComponent( "javafx.scene.control.Label" ) {
59
				name: "titleLabel"
60
				"text": new FormMessage( null, "LinkDialog.titleLabel.text" )
61
				auxiliary() {
62
					"JavaCodeGenerator.variableLocal": true
63
				}
64
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
65
				"value": "cell 0 2"
66
			} )
67
			add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) {
68
				name: "titleField"
69
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
70
				"value": "cell 1 2 3 1"
71
			} )
72
			add( new FormComponent( "javafx.scene.control.Label" ) {
73
				name: "previewLabel"
74
				"text": new FormMessage( null, "LinkDialog.previewLabel.text" )
75
				auxiliary() {
76
					"JavaCodeGenerator.variableLocal": true
77
				}
78
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
79
				"value": "cell 0 3"
80
			} )
81
			add( new FormComponent( "javafx.scene.control.Label" ) {
82
				name: "previewField"
83
			}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
84
				"value": "cell 1 3 3 1"
85
			} )
86
		}, new FormLayoutConstraints( null ) {
87
			"location": new javafx.geometry.Point2D( 0.0, 0.0 )
88
			"size": new javafx.geometry.Dimension2D( 500.0, 300.0 )
89
		} )
90
	}
91
}
192
A src/main/java/com/scrivenvar/editors/EditorPane.java
1
/*
2
 * Copyright 2016 Karl Tauber and White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.editors;
29
30
import com.scrivenvar.AbstractPane;
31
import java.nio.file.Path;
32
import java.util.function.Consumer;
33
import javafx.application.Platform;
34
import javafx.beans.property.ObjectProperty;
35
import javafx.beans.property.SimpleObjectProperty;
36
import javafx.beans.value.ChangeListener;
37
import javafx.event.Event;
38
import javafx.scene.control.ScrollPane;
39
import javafx.scene.input.InputEvent;
40
import org.fxmisc.flowless.VirtualizedScrollPane;
41
import org.fxmisc.richtext.StyleClassedTextArea;
42
import org.fxmisc.undo.UndoManager;
43
import org.fxmisc.wellbehaved.event.EventPattern;
44
import org.fxmisc.wellbehaved.event.InputMap;
45
import static org.fxmisc.wellbehaved.event.InputMap.consume;
46
import org.fxmisc.wellbehaved.event.Nodes;
47
48
/**
49
 * Represents common editing features for various types of text editors.
50
 *
51
 * @author White Magic Software, Ltd.
52
 */
53
public class EditorPane extends AbstractPane {
54
55
  private StyleClassedTextArea editor;
56
  private VirtualizedScrollPane<StyleClassedTextArea> scrollPane;
57
  private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
58
59
  /**
60
   * Set when entering variable edit mode; retrieved upon exiting.
61
   */
62
  private InputMap<InputEvent> nodeMap;
63
64
  @Override
65
  public void requestFocus() {
66
    Platform.runLater( () -> getEditor().requestFocus() );
67
  }
68
69
  public void undo() {
70
    getUndoManager().undo();
71
  }
72
73
  public void redo() {
74
    getUndoManager().redo();
75
  }
76
77
  public UndoManager getUndoManager() {
78
    return getEditor().getUndoManager();
79
  }
80
81
  public String getText() {
82
    return getEditor().getText();
83
  }
84
85
  public void setText( final String text ) {
86
    getEditor().deselect();
87
    getEditor().replaceText( text );
88
    getUndoManager().mark();
89
  }
90
91
  /**
92
   * Call to hook into changes to the text area.
93
   *
94
   * @param listener Receives editor text change events.
95
   */
96
  public void addTextChangeListener( final ChangeListener<? super String> listener ) {
97
    getEditor().textProperty().addListener( listener );
98
  }
99
100
  /**
101
   * Call to listen for when the caret moves to another paragraph.
102
   *
103
   * @param listener Receives paragraph change events.
104
   */
105
  public void addCaretParagraphListener(
106
    final ChangeListener<? super Integer> listener ) {
107
    getEditor().currentParagraphProperty().addListener( listener );
108
  }
109
110
  /**
111
   * This method adds listeners to editor events.
112
   *
113
   * @param <T> The event type.
114
   * @param <U> The consumer type for the given event type.
115
   * @param event The event of interest.
116
   * @param consumer The method to call when the event happens.
117
   */
118
  public <T extends Event, U extends T> void addEventListener(
119
    final EventPattern<? super T, ? extends U> event,
120
    final Consumer<? super U> consumer ) {
121
    Nodes.addInputMap( getEditor(), consume( event, consumer ) );
122
  }
123
124
  /**
125
   * This method adds listeners to editor events that can be removed without
126
   * affecting the original listeners (i.e., the original lister is restored on
127
   * a call to removeEventListener).
128
   *
129
   * @param map The map of methods to events.
130
   */
131
  @SuppressWarnings( "unchecked" )
132
  public void addEventListener( final InputMap<InputEvent> map ) {
133
    this.nodeMap = (InputMap<InputEvent>)getInputMap();
134
    Nodes.addInputMap( getEditor(), map );
135
  }
136
137
  /**
138
   * This method removes listeners to editor events and restores the default
139
   * handler.
140
   *
141
   * @param map The map of methods to events.
142
   */
143
  public void removeEventListener( final InputMap<InputEvent> map ) {
144
    Nodes.removeInputMap( getEditor(), map );
145
    Nodes.addInputMap( getEditor(), this.nodeMap );
146
  }
147
148
  /**
149
   * Returns the value for "org.fxmisc.wellbehaved.event.inputmap".
150
   *
151
   * @return An input map of input events.
152
   */
153
  private Object getInputMap() {
154
    return getEditor().getProperties().get( getInputMapKey() );
155
  }
156
157
  /**
158
   * Returns the hashmap key entry for the input map.
159
   *
160
   * @return "org.fxmisc.wellbehaved.event.inputmap"
161
   */
162
  private String getInputMapKey() {
163
    return "org.fxmisc.wellbehaved.event.inputmap";
164
  }
165
166
  public void scrollToTop() {
167
    getEditor().moveTo( 0 );
168
  }
169
170
  private void setEditor( StyleClassedTextArea textArea ) {
171
    this.editor = textArea;
172
  }
173
174
  public synchronized StyleClassedTextArea getEditor() {
175
    if( this.editor == null ) {
176
      setEditor( createTextArea() );
177
    }
178
179
    return this.editor;
180
  }
181
182
  /**
183
   * Returns the scroll pane that contains the text area.
184
   *
185
   * @return The scroll pane that contains the content to edit.
186
   */
187
  public synchronized VirtualizedScrollPane<StyleClassedTextArea> getScrollPane() {
188
    if( this.scrollPane == null ) {
189
      this.scrollPane = createScrollPane();
190
    }
191
192
    return this.scrollPane;
193
  }
194
195
  protected VirtualizedScrollPane<StyleClassedTextArea> createScrollPane() {
196
    final VirtualizedScrollPane<StyleClassedTextArea> pane
197
      = new VirtualizedScrollPane<>( getEditor() );
198
    pane.setVbarPolicy( ScrollPane.ScrollBarPolicy.ALWAYS );
199
200
    return pane;
201
  }
202
203
  protected StyleClassedTextArea createTextArea() {
204
    return new StyleClassedTextArea( false );
205
  }
206
207
  public Path getPath() {
208
    return this.path.get();
209
  }
210
211
  public void setPath( final Path path ) {
212
    this.path.set( path );
213
  }
214
215
  public ObjectProperty<Path> pathProperty() {
216
    return this.path;
217
  }
218
}
1219
A src/main/java/com/scrivenvar/editors/VariableNameInjector.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.editors;
29
30
import com.scrivenvar.FileEditorTabPane;
31
import com.scrivenvar.Services;
32
import com.scrivenvar.decorators.VariableDecorator;
33
import com.scrivenvar.decorators.YamlVariableDecorator;
34
import com.scrivenvar.definition.DefinitionPane;
35
import com.scrivenvar.definition.VariableTreeItem;
36
import static com.scrivenvar.definition.yaml.YamlParser.SEPARATOR;
37
import com.scrivenvar.service.Settings;
38
import static com.scrivenvar.util.Lists.getFirst;
39
import static com.scrivenvar.util.Lists.getLast;
40
import static java.lang.Character.isSpaceChar;
41
import static java.lang.Character.isWhitespace;
42
import static java.lang.Math.min;
43
import java.util.function.Consumer;
44
import javafx.collections.ObservableList;
45
import javafx.event.Event;
46
import javafx.scene.control.IndexRange;
47
import javafx.scene.control.TreeItem;
48
import javafx.scene.input.InputEvent;
49
import javafx.scene.input.KeyCode;
50
import static javafx.scene.input.KeyCode.AT;
51
import static javafx.scene.input.KeyCode.DIGIT2;
52
import static javafx.scene.input.KeyCode.ENTER;
53
import static javafx.scene.input.KeyCode.MINUS;
54
import static javafx.scene.input.KeyCode.SPACE;
55
import static javafx.scene.input.KeyCombination.CONTROL_DOWN;
56
import static javafx.scene.input.KeyCombination.SHIFT_DOWN;
57
import javafx.scene.input.KeyEvent;
58
import org.fxmisc.richtext.StyledTextArea;
59
import org.fxmisc.wellbehaved.event.EventPattern;
60
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
61
import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped;
62
import org.fxmisc.wellbehaved.event.InputMap;
63
import static org.fxmisc.wellbehaved.event.InputMap.consume;
64
import static org.fxmisc.wellbehaved.event.InputMap.sequence;
65
import static com.scrivenvar.util.Lists.getFirst;
66
import static com.scrivenvar.util.Lists.getLast;
67
import static java.lang.Character.isSpaceChar;
68
import static java.lang.Character.isWhitespace;
69
import static java.lang.Math.min;
70
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
71
import static org.fxmisc.wellbehaved.event.EventPattern.keyTyped;
72
import static org.fxmisc.wellbehaved.event.InputMap.consume;
73
74
/**
75
 * Provides the logic for injecting variable names within the editor.
76
 *
77
 * @author White Magic Software, Ltd.
78
 */
79
public class VariableNameInjector {
80
81
  public static final int DEFAULT_MAX_VAR_LENGTH = 64;
82
83
  private static final int NO_DIFFERENCE = -1;
84
85
  private final Settings settings = Services.load( Settings.class );
86
87
  /**
88
   * Used to capture keyboard events once the user presses @.
89
   */
90
  private InputMap<InputEvent> keyboardMap;
91
92
  private FileEditorTabPane fileEditorPane;
93
  private DefinitionPane definitionPane;
94
95
  /**
96
   * Position of the variable in the text when in variable mode (0 by default).
97
   */
98
  private int initialCaretPosition;
99
100
  public VariableNameInjector(
101
    final FileEditorTabPane editorPane,
102
    final DefinitionPane definitionPane ) {
103
    setFileEditorPane( editorPane );
104
    setDefinitionPane( definitionPane );
105
106
    initKeyboardEventListeners();
107
  }
108
109
  /**
110
   * Traps keys for performing various short-cut tasks, such as @-mode variable
111
   * insertion and control+space for variable autocomplete.
112
   *
113
   * @ key is pressed, a new keyboard map is inserted in place of the current
114
   * map -- this class goes into "variable edit mode" (a.k.a. vMode).
115
   *
116
   * @see createKeyboardMap()
117
   */
118
  private void initKeyboardEventListeners() {
119
    addEventListener( keyPressed( SPACE, CONTROL_DOWN ), this::autocomplete );
120
121
    // @ key in Linux?
122
    addEventListener( keyPressed( DIGIT2, SHIFT_DOWN ), this::vMode );
123
    // @ key in Windows.
124
    addEventListener( keyPressed( AT ), this::vMode );
125
  }
126
127
  /**
128
   * The @ symbol is a short-cut to inserting a YAML variable reference.
129
   *
130
   * @param e Superfluous information about the key that was pressed.
131
   */
132
  private void vMode( KeyEvent e ) {
133
    setInitialCaretPosition();
134
    vModeStart();
135
    vModeAutocomplete();
136
  }
137
138
  /**
139
   * Receives key presses until the user completes the variable selection. This
140
   * allows the arrow keys to be used for selecting variables.
141
   *
142
   * @param e The key that was pressed.
143
   */
144
  private void vModeKeyPressed( KeyEvent e ) {
145
    final KeyCode keyCode = e.getCode();
146
147
    switch( keyCode ) {
148
      case BACK_SPACE:
149
        // Don't decorate the variable upon exiting vMode.
150
        vModeBackspace();
151
        break;
152
153
      case ESCAPE:
154
        // Don't decorate the variable upon exiting vMode.
155
        vModeStop();
156
        break;
157
158
      case ENTER:
159
      case PERIOD:
160
      case RIGHT:
161
      case END:
162
        // Stop at a leaf node, ENTER means accept.
163
        if( vModeConditionalComplete() && keyCode == ENTER ) {
164
          vModeStop();
165
166
          // Decorate the variable upon exiting vMode.
167
          decorateVariable();
168
        }
169
        break;
170
171
      case UP:
172
        cyclePathPrev();
173
        break;
174
175
      case DOWN:
176
        cyclePathNext();
177
        break;
178
179
      default:
180
        vModeFilterKeyPressed( e );
181
        break;
182
    }
183
184
    e.consume();
185
  }
186
187
  private void vModeBackspace() {
188
    deleteSelection();
189
190
    // Break out of variable mode by back spacing to the original position.
191
    if( getCurrentCaretPosition() > getInitialCaretPosition() ) {
192
      vModeAutocomplete();
193
    } else {
194
      vModeStop();
195
    }
196
  }
197
198
  /**
199
   * Updates the text with the path selected (or typed) by the user.
200
   */
201
  private void vModeAutocomplete() {
202
    final TreeItem<String> node = getCurrentNode();
203
204
    if( !node.isLeaf() ) {
205
      final String word = getLastPathWord();
206
      final String label = node.getValue();
207
      final int delta = difference( label, word );
208
      final String remainder = delta == NO_DIFFERENCE
209
        ? label
210
        : label.substring( delta );
211
212
      final StyledTextArea textArea = getEditor();
213
      final int posBegan = getCurrentCaretPosition();
214
      final int posEnded = posBegan + remainder.length();
215
216
      textArea.replaceSelection( remainder );
217
218
      if( posEnded - posBegan > 0 ) {
219
        textArea.selectRange( posEnded, posBegan );
220
      }
221
222
      expand( node );
223
    }
224
  }
225
226
  /**
227
   * Only variable name keys can pass through the filter. This is called when
228
   * the user presses a key.
229
   *
230
   * @param e The key that was pressed.
231
   */
232
  private void vModeFilterKeyPressed( final KeyEvent e ) {
233
    if( isVariableNameKey( e ) ) {
234
      typed( e.getText() );
235
    }
236
  }
237
238
  /**
239
   * Performs an autocomplete depending on whether the user has finished typing
240
   * in a word. If there is a selected range, then this will complete the most
241
   * recent word and jump to the next child.
242
   *
243
   * @return true The auto-completed node was a terminal node.
244
   */
245
  private boolean vModeConditionalComplete() {
246
    acceptPath();
247
248
    final TreeItem<String> node = getCurrentNode();
249
    final boolean terminal = isTerminal( node );
250
251
    if( !terminal ) {
252
      typed( SEPARATOR );
253
    }
254
255
    return terminal;
256
  }
257
258
  /**
259
   * Pressing control+space will find a node that matches the current word and
260
   * substitute the YAML variable reference. This is called when the user is not
261
   * editing in vMode.
262
   *
263
   * @param e Ignored -- it can only be Ctrl+Space.
264
   */
265
  private void autocomplete( KeyEvent e ) {
266
    final String paragraph = getCaretParagraph();
267
    final int[] boundaries = getWordBoundaries( paragraph );
268
    final String word = paragraph.substring( boundaries[ 0 ], boundaries[ 1 ] );
269
270
    final VariableTreeItem<String> leaf = findLeaf( word );
271
272
    if( leaf != null ) {
273
      replaceText( boundaries[ 0 ], boundaries[ 1 ], leaf.toPath() );
274
      decorateVariable();
275
      expand( leaf );
276
    }
277
  }
278
279
  /**
280
   * Called when autocomplete finishes on a valid leaf or when the user presses
281
   * Enter to finish manual autocomplete.
282
   */
283
  private void decorateVariable() {
284
    // A little bit of duplication...
285
    final String paragraph = getCaretParagraph();
286
    final int[] boundaries = getWordBoundaries( paragraph );
287
    final String old = paragraph.substring( boundaries[ 0 ], boundaries[ 1 ] );
288
289
    final String newVariable = getVariableDecorator().decorate( old );
290
291
    final int posEnded = getCurrentCaretPosition();
292
    final int posBegan = posEnded - old.length();
293
294
    getEditor().replaceText( posBegan, posEnded, newVariable );
295
  }
296
297
  /**
298
   * Updates the text at the given position within the current paragraph.
299
   *
300
   * @param posBegan The starting index in the paragraph text to replace.
301
   * @param posEnded The ending index in the paragraph text to replace.
302
   * @param text Overwrite the paragraph substring with this text.
303
   */
304
  private void replaceText(
305
    final int posBegan, final int posEnded, final String text ) {
306
    final int p = getCurrentParagraph();
307
308
    getEditor().replaceText( p, posBegan, p, posEnded, text );
309
  }
310
311
  /**
312
   * Returns the caret's current paragraph position.
313
   *
314
   * @return A number greater than or equal to 0.
315
   */
316
  private int getCurrentParagraph() {
317
    return getEditor().getCurrentParagraph();
318
  }
319
320
  /**
321
   * Returns current word boundary indexes into the current paragraph, including
322
   * punctuation.
323
   *
324
   * @param p The paragraph wherein to hunt word boundaries.
325
   * @param offset The offset into the paragraph to begin scanning left and
326
   * right.
327
   *
328
   * @return The starting and ending index of the word closest to the caret.
329
   */
330
  private int[] getWordBoundaries( final String p, final int offset ) {
331
    // Remove dashes, but retain hyphens. Retain same number of characters
332
    // to preserve relative indexes.
333
    final String paragraph = p.replace( "---", "   " ).replace( "--", "  " );
334
335
    return getWordAt( paragraph, offset );
336
  }
337
338
  /**
339
   * Helper method to get the word boundaries for the current paragraph.
340
   *
341
   * @param paragraph
342
   *
343
   * @return
344
   */
345
  private int[] getWordBoundaries( final String paragraph ) {
346
    return getWordBoundaries( paragraph, getCurrentCaretColumn() );
347
  }
348
349
  /**
350
   * Given an arbitrary offset into a string, this returns the word at that
351
   * index. The inputs and outputs include:
352
   *
353
   * <ul>
354
   * <li>surrounded by space: <code>hello | world!</code> ("");</li>
355
   * <li>end of word: <code>hello| world!</code> ("hello");</li>
356
   * <li>start of a word: <code>hello |world!</code> ("world!");</li>
357
   * <li>within a word: <code>hello wo|rld!</code> ("world!");</li>
358
   * <li>end of a paragraph: <code>hello world!|</code> ("world!");</li>
359
   * <li>start of a paragraph: <code>|hello world!</code> ("hello!"); or</li>
360
   * <li>after punctuation: <code>hello world!|</code> ("world!").</li>
361
   * </ul>
362
   *
363
   * @param p The string to scan for a word.
364
   * @param offset The offset within s to begin searching for the nearest word
365
   * boundary, must not be out of bounds of s.
366
   *
367
   * @return The word in s at the offset.
368
   *
369
   * @see getWordBegan( String, int )
370
   * @see getWordEnded( String, int )
371
   */
372
  private int[] getWordAt( final String p, final int offset ) {
373
    return new int[]{ getWordBegan( p, offset ), getWordEnded( p, offset ) };
374
  }
375
376
  /**
377
   * Returns the index into s where a word begins.
378
   *
379
   * @param s Never null.
380
   * @param offset Index into s to begin searching backwards for a word
381
   * boundary.
382
   *
383
   * @return The index where a word begins.
384
   */
385
  private int getWordBegan( final String s, int offset ) {
386
    while( offset > 0 && isBoundary( s.charAt( offset - 1 ) ) ) {
387
      offset--;
388
    }
389
390
    return offset;
391
  }
392
393
  /**
394
   * Returns the index into s where a word ends.
395
   *
396
   * @param s Never null.
397
   * @param offset Index into s to begin searching forwards for a word boundary.
398
   *
399
   * @return The index where a word ends.
400
   */
401
  private int getWordEnded( final String s, int offset ) {
402
    final int length = s.length();
403
404
    while( offset < length && isBoundary( s.charAt( offset ) ) ) {
405
      offset++;
406
    }
407
408
    return offset;
409
  }
410
411
  /**
412
   * Returns true if the given character can be reasonably expected to be part
413
   * of a word, including punctuation marks.
414
   *
415
   * @param c The character to compare.
416
   *
417
   * @return false The character is a space character.
418
   */
419
  private boolean isBoundary( final char c ) {
420
    return !isSpaceChar( c );
421
  }
422
423
  /**
424
   * Returns the text for the paragraph that contains the caret.
425
   *
426
   * @return A non-null string, possibly empty.
427
   */
428
  private String getCaretParagraph() {
429
    return getEditor().getText( getCurrentParagraph() );
430
  }
431
432
  /**
433
   * Returns true if the node has children that can be selected (i.e., any
434
   * non-leaves).
435
   *
436
   * @param <T> The type that the TreeItem contains.
437
   * @param node The node to test for terminality.
438
   *
439
   * @return true The node has one branch and its a leaf.
440
   */
441
  private <T> boolean isTerminal( final TreeItem<T> node ) {
442
    final ObservableList<TreeItem<T>> branches = node.getChildren();
443
444
    return branches.size() == 1 && branches.get( 0 ).isLeaf();
445
  }
446
447
  /**
448
   * Inserts text that the user typed at the current caret position, then
449
   * performs an autocomplete for the variable name.
450
   *
451
   * @param text The text to insert, never null.
452
   */
453
  private void typed( final String text ) {
454
    getEditor().replaceSelection( text );
455
    vModeAutocomplete();
456
  }
457
458
  /**
459
   * Called when the user presses either End or Enter key.
460
   */
461
  private void acceptPath() {
462
    final IndexRange range = getSelectionRange();
463
464
    if( range != null ) {
465
      final int rangeEnd = range.getEnd();
466
      final StyledTextArea textArea = getEditor();
467
      textArea.deselect();
468
      textArea.moveTo( rangeEnd );
469
    }
470
  }
471
472
  /**
473
   * Replaces the entirety of the existing path (from the initial caret
474
   * position) with the given path.
475
   *
476
   * @param oldPath The path to replace.
477
   * @param newPath The replacement path.
478
   */
479
  private void replacePath( final String oldPath, final String newPath ) {
480
    final StyledTextArea textArea = getEditor();
481
    final int posBegan = getInitialCaretPosition();
482
    final int posEnded = posBegan + oldPath.length();
483
484
    textArea.deselect();
485
    textArea.replaceText( posBegan, posEnded, newPath );
486
  }
487
488
  /**
489
   * Called when the user presses the Backspace key.
490
   */
491
  private void deleteSelection() {
492
    final StyledTextArea textArea = getEditor();
493
    textArea.replaceSelection( "" );
494
    textArea.deletePreviousChar();
495
  }
496
497
  /**
498
   * Cycles the selected text through the nodes.
499
   *
500
   * @param direction true - next; false - previous
501
   */
502
  private void cycleSelection( final boolean direction ) {
503
    final TreeItem<String> node = getCurrentNode();
504
505
    // Find the sibling for the current selection and replace the current
506
    // selection with the sibling's value
507
    TreeItem< String> cycled = direction
508
      ? node.nextSibling()
509
      : node.previousSibling();
510
511
    // When cycling at the end (or beginning) of the list, jump to the first
512
    // (or last) sibling depending on the cycle direction.
513
    if( cycled == null ) {
514
      cycled = direction ? getFirstSibling( node ) : getLastSibling( node );
515
    }
516
517
    final String path = getCurrentPath();
518
    final String cycledWord = cycled.getValue();
519
    final String word = getLastPathWord();
520
    final int index = path.indexOf( word );
521
    final String cycledPath = path.substring( 0, index ) + cycledWord;
522
523
    expand( cycled );
524
    replacePath( path, cycledPath );
525
  }
526
527
  /**
528
   * Cycles to the next sibling of the currently selected tree node.
529
   */
530
  private void cyclePathNext() {
531
    cycleSelection( true );
532
  }
533
534
  /**
535
   * Cycles to the previous sibling of the currently selected tree node.
536
   */
537
  private void cyclePathPrev() {
538
    cycleSelection( false );
539
  }
540
541
  /**
542
   * Returns the variable name (or as much as has been typed so far). Returns
543
   * all the characters from the initial caret column to the the first
544
   * whitespace character. This will return a path that contains zero or more
545
   * separators.
546
   *
547
   * @return A non-null string, possibly empty.
548
   */
549
  private String getCurrentPath() {
550
    final String s = extractTextChunk();
551
    final int length = s.length();
552
553
    int i = 0;
554
555
    while( i < length && !isWhitespace( s.charAt( i ) ) ) {
556
      i++;
557
    }
558
559
    return s.substring( 0, i );
560
  }
561
562
  private <T> ObservableList<TreeItem<T>> getSiblings(
563
    final TreeItem<T> item ) {
564
    final TreeItem<T> parent = item.getParent();
565
    return parent == null ? item.getChildren() : parent.getChildren();
566
  }
567
568
  private <T> TreeItem<T> getFirstSibling( final TreeItem<T> item ) {
569
    return getFirst( getSiblings( item ), item );
570
  }
571
572
  private <T> TreeItem<T> getLastSibling( final TreeItem<T> item ) {
573
    return getLast( getSiblings( item ), item );
574
  }
575
576
  /**
577
   * Returns the caret position as an offset into the text.
578
   *
579
   * @return A value from 0 to the length of the text (minus one).
580
   */
581
  private int getCurrentCaretPosition() {
582
    return getEditor().getCaretPosition();
583
  }
584
585
  /**
586
   * Returns the caret position within the current paragraph.
587
   *
588
   * @return A value from 0 to the length of the current paragraph.
589
   */
590
  private int getCurrentCaretColumn() {
591
    return getEditor().getCaretColumn();
592
  }
593
594
  /**
595
   * Returns the last word from the path.
596
   *
597
   * @return The last token.
598
   */
599
  private String getLastPathWord() {
600
    String path = getCurrentPath();
601
602
    int i = path.indexOf( SEPARATOR );
603
604
    while( i > 0 ) {
605
      path = path.substring( i + 1 );
606
      i = path.indexOf( SEPARATOR );
607
    }
608
609
    return path;
610
  }
611
612
  /**
613
   * Returns text from the initial caret position until some arbitrarily long
614
   * number of characters. The number of characters extracted will be
615
   * getMaxVarLength, or fewer, depending on how many characters remain to be
616
   * extracted. The result from this method is trimmed to the first whitespace
617
   * character.
618
   *
619
   * @return A chunk of text that includes all the words representing a path,
620
   * and then some.
621
   */
622
  private String extractTextChunk() {
623
    final StyledTextArea textArea = getEditor();
624
    final int textBegan = getInitialCaretPosition();
625
    final int remaining = textArea.getLength() - textBegan;
626
    final int textEnded = min( remaining, getMaxVarLength() );
627
628
    return textArea.getText( textBegan, textEnded );
629
  }
630
631
  /**
632
   * Returns the node for the current path.
633
   */
634
  private TreeItem<String> getCurrentNode() {
635
    return findNode( getCurrentPath() );
636
  }
637
638
  /**
639
   * Finds the node that most closely matches the given path.
640
   *
641
   * @param path The path that represents a node.
642
   *
643
   * @return The node for the path, or the root node if the path could not be
644
   * found, but never null.
645
   */
646
  private TreeItem<String> findNode( final String path ) {
647
    return getDefinitionPane().findNode( path );
648
  }
649
650
  /**
651
   * Finds the first leaf having a value that starts with the given text.
652
   *
653
   * @param text The text to find in the definition tree.
654
   *
655
   * @return The leaf that starts with the given text, or null if not found.
656
   */
657
  private VariableTreeItem<String> findLeaf( final String text ) {
658
    return getDefinitionPane().findLeaf( text );
659
  }
660
661
  /**
662
   * Used to ignore typed keys in favour of trapping pressed keys.
663
   *
664
   * @param e The key that was typed.
665
   */
666
  private void vModeKeyTyped( KeyEvent e ) {
667
    e.consume();
668
  }
669
670
  /**
671
   * Used to lazily initialize the keyboard map.
672
   *
673
   * @return Mappings for keyTyped and keyPressed.
674
   */
675
  protected InputMap<InputEvent> createKeyboardMap() {
676
    return sequence(
677
      consume( keyTyped(), this::vModeKeyTyped ),
678
      consume( keyPressed(), this::vModeKeyPressed )
679
    );
680
  }
681
682
  private InputMap<InputEvent> getKeyboardMap() {
683
    if( this.keyboardMap == null ) {
684
      this.keyboardMap = createKeyboardMap();
685
    }
686
687
    return this.keyboardMap;
688
  }
689
690
  /**
691
   * Collapses the tree then expands and selects the given node.
692
   *
693
   * @param node The node to expand.
694
   */
695
  private void expand( final TreeItem<String> node ) {
696
    final DefinitionPane pane = getDefinitionPane();
697
    pane.collapse();
698
    pane.expand( node );
699
    pane.select( node );
700
  }
701
702
  /**
703
   * Returns true iff the key code the user typed can be used as part of a YAML
704
   * variable name.
705
   *
706
   * @param keyEvent Keyboard key press event information.
707
   *
708
   * @return true The key is a value that can be inserted into the text.
709
   */
710
  private boolean isVariableNameKey( final KeyEvent keyEvent ) {
711
    final KeyCode kc = keyEvent.getCode();
712
713
    return (kc.isLetterKey()
714
      || kc.isDigitKey()
715
      || (keyEvent.isShiftDown() && kc == MINUS))
716
      && !keyEvent.isControlDown();
717
  }
718
719
  /**
720
   * Starts to capture user input events.
721
   */
722
  private void vModeStart() {
723
    addEventListener( getKeyboardMap() );
724
  }
725
726
  /**
727
   * Restores capturing of user input events to the previous event listener.
728
   * Also asks the processing chain to modify the variable text into a
729
   * machine-readable variable based on the format required by the file type.
730
   * For example, a Markdown file (.md) will substitute a $VAR$ name while an R
731
   * file (.Rmd, .Rxml) will use `r#xVAR`.
732
   */
733
  private void vModeStop() {
734
    removeEventListener( getKeyboardMap() );
735
  }
736
737
  private VariableDecorator getVariableDecorator() {
738
    return new YamlVariableDecorator();
739
  }
740
741
  /**
742
   * Returns the index where the two strings diverge.
743
   *
744
   * @param s1 The string that could be a substring of s2, null allowed.
745
   * @param s2 The string that could be a substring of s1, null allowed.
746
   *
747
   * @return NO_DIFFERENCE if the strings are the same, otherwise the index
748
   * where they differ.
749
   */
750
  @SuppressWarnings( "StringEquality" )
751
  private int difference( final CharSequence s1, final CharSequence s2 ) {
752
    if( s1 == s2 ) {
753
      return NO_DIFFERENCE;
754
    }
755
756
    if( s1 == null || s2 == null ) {
757
      return 0;
758
    }
759
760
    int i = 0;
761
    final int limit = min( s1.length(), s2.length() );
762
763
    while( i < limit && s1.charAt( i ) == s2.charAt( i ) ) {
764
      i++;
765
    }
766
767
    // If one string was shorter than the other, that's where they differ.
768
    return i;
769
  }
770
771
  /**
772
   * Delegates to the file editor pane, and, ultimately, to its text area.
773
   */
774
  private <T extends Event, U extends T> void addEventListener(
775
    final EventPattern<? super T, ? extends U> event,
776
    final Consumer<? super U> consumer ) {
777
    getFileEditorPane().addEventListener( event, consumer );
778
  }
779
780
  /**
781
   * Delegates to the file editor pane, and, ultimately, to its text area.
782
   *
783
   * @param map The map of methods to events.
784
   */
785
  private void addEventListener( final InputMap<InputEvent> map ) {
786
    getFileEditorPane().addEventListener( map );
787
  }
788
789
  private void removeEventListener( final InputMap<InputEvent> map ) {
790
    getFileEditorPane().removeEventListener( map );
791
  }
792
793
  /**
794
   * Returns the position of the caret when variable mode editing was requested.
795
   *
796
   * @return The variable mode caret position.
797
   */
798
  private int getInitialCaretPosition() {
799
    return this.initialCaretPosition;
800
  }
801
802
  /**
803
   * Sets the position of the caret when variable mode editing was requested.
804
   * Stores the current position because only the text that comes afterwards is
805
   * a suitable variable reference.
806
   *
807
   * @return The variable mode caret position.
808
   */
809
  private void setInitialCaretPosition() {
810
    this.initialCaretPosition = getEditor().getCaretPosition();
811
  }
812
813
  private StyledTextArea getEditor() {
814
    return getFileEditorPane().getEditor();
815
  }
816
817
  public FileEditorTabPane getFileEditorPane() {
818
    return this.fileEditorPane;
819
  }
820
821
  private void setFileEditorPane( final FileEditorTabPane fileEditorPane ) {
822
    this.fileEditorPane = fileEditorPane;
823
  }
824
825
  private DefinitionPane getDefinitionPane() {
826
    return this.definitionPane;
827
  }
828
829
  private void setDefinitionPane( final DefinitionPane definitionPane ) {
830
    this.definitionPane = definitionPane;
831
  }
832
833
  private IndexRange getSelectionRange() {
834
    return getEditor().getSelection();
835
  }
836
837
  /**
838
   * Don't look ahead too far when trying to find the end of a node.
839
   *
840
   * @return 512 by default.
841
   */
842
  private int getMaxVarLength() {
843
    return getSettings().getSetting(
844
      "editor.variable.maxLength", DEFAULT_MAX_VAR_LENGTH );
845
  }
846
847
  private Settings getSettings() {
848
    return this.settings;
849
  }
850
}
1851
A src/main/java/com/scrivenvar/editors/markdown/HyperlinkModel.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.editors.markdown;
29
30
import com.vladsch.flexmark.ast.Link;
31
32
/**
33
 * Represents the model for a hyperlink: text and url text.
34
 *
35
 * @author White Magic Software, Ltd.
36
 */
37
public class HyperlinkModel {
38
39
  private String text;
40
  private String url;
41
  private String title;
42
43
  /**
44
   * Constructs a new hyperlink model in Markdown format by default with no
45
   * title (i.e., tooltip).
46
   *
47
   * @param text The hyperlink text displayed (e.g., displayed to the user).
48
   * @param url The destination URL (e.g., when clicked).
49
   */
50
  public HyperlinkModel( final String text, final String url ) {
51
    this( text, url, null );
52
  }
53
54
  /**
55
   * Constructs a new hyperlink model for the given AST link.
56
   * 
57
   * @param link A markdown link.
58
   */
59
  public HyperlinkModel( final Link link ) {
60
    this(
61
      link.getText().toString(),
62
      link.getUrl().toString(),
63
      link.getTitle().toString()
64
    );
65
  }
66
67
  /**
68
   * Constructs a new hyperlink model in Markdown format by default.
69
   *
70
   * @param text The hyperlink text displayed (e.g., displayed to the user).
71
   * @param url The destination URL (e.g., when clicked).
72
   * @param title The hyperlink title (e.g., shown as a tooltip).
73
   */
74
  public HyperlinkModel( final String text, final String url, final String title ) {
75
    setText( text );
76
    setUrl( url );
77
    setTitle( title );
78
  }
79
80
  /**
81
   * Returns the string in Markdown format by default.
82
   *
83
   * @return A markdown version of the hyperlink.
84
   */
85
  @Override
86
  public String toString() {
87
    String format = "%s%s%s";
88
89
    if( hasText() ) {
90
      format = "[%s]" + (hasTitle() ? "(%s \"%s\")" : "(%s%s)");
91
    }
92
93
    // Becomes ""+URL+"" if no text is set.
94
    // Becomes [TITLE]+(URL)+"" if no title is set.
95
    // Becomes [TITLE]+(URL+ \"TITLE\") if title is set.
96
    return String.format( format, getText(), getUrl(), getTitle() );
97
  }
98
99
  public final void setText( final String text ) {
100
    this.text = nullSafe( text );
101
  }
102
103
  public final void setUrl( final String url ) {
104
    this.url = nullSafe( url );
105
  }
106
107
  public final void setTitle( final String title ) {
108
    this.title = nullSafe( title );
109
  }
110
111
  /**
112
   * Answers whether text has been set for the hyperlink.
113
   *
114
   * @return true This is a text link.
115
   */
116
  public boolean hasText() {
117
    return !getText().isEmpty();
118
  }
119
120
  /**
121
   * Answers whether a title (tooltip) has been set for the hyperlink.
122
   *
123
   * @return true There is a title.
124
   */
125
  public boolean hasTitle() {
126
    return !getTitle().isEmpty();
127
  }
128
129
  public String getText() {
130
    return this.text;
131
  }
132
133
  public String getUrl() {
134
    return this.url;
135
  }
136
137
  public String getTitle() {
138
    return this.title;
139
  }
140
141
  private String nullSafe( final String s ) {
142
    return s == null ? "" : s;
143
  }
144
}
1145
A src/main/java/com/scrivenvar/editors/markdown/LinkVisitor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.editors.markdown;
29
30
import com.vladsch.flexmark.ast.Link;
31
import com.vladsch.flexmark.ast.Node;
32
import com.vladsch.flexmark.ast.NodeVisitor;
33
import com.vladsch.flexmark.ast.VisitHandler;
34
35
/**
36
 * @author White Magic Software, Ltd.
37
 */
38
public class LinkVisitor {
39
40
  private NodeVisitor visitor;
41
  private Link link;
42
  private final int offset;
43
44
  /**
45
   * Creates a hyperlink given an offset into a paragraph and the markdown AST
46
   * link node.
47
   *
48
   * @param index Index into the paragraph that indicates the hyperlink to
49
   * change.
50
   */
51
  public LinkVisitor( final int index ) {
52
    this.offset = index;
53
  }
54
55
  public Link process( final Node root ) {
56
    getVisitor().visit( root );
57
    return getLink();
58
  }
59
60
  /**
61
   *
62
   * @param link Not null.
63
   */
64
  private void visit( final Link link ) {
65
    final int began = link.getStartOffset();
66
    final int ended = link.getEndOffset();
67
    final int index = getOffset();
68
69
    if( index >= began && index <= ended ) {
70
      setLink( link );
71
    }
72
  }
73
74
  private synchronized NodeVisitor getVisitor() {
75
    if( this.visitor == null ) {
76
      this.visitor = createVisitor();
77
    }
78
79
    return this.visitor;
80
  }
81
82
  protected NodeVisitor createVisitor() {
83
    return new NodeVisitor(
84
      new VisitHandler<>( Link.class, LinkVisitor.this::visit ) );
85
  }
86
87
  private Link getLink() {
88
    return this.link;
89
  }
90
91
  private void setLink( final Link link ) {
92
    this.link = link;
93
  }
94
95
  public int getOffset() {
96
    return this.offset;
97
  }
98
}
199
A src/main/java/com/scrivenvar/editors/markdown/MarkdownEditorPane.java
1
/*
2
 * Copyright 2016 Karl Tauber and White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.editors.markdown;
29
30
import com.scrivenvar.dialogs.ImageDialog;
31
import com.scrivenvar.dialogs.LinkDialog;
32
import com.scrivenvar.editors.EditorPane;
33
import com.scrivenvar.processors.MarkdownProcessor;
34
import static com.scrivenvar.util.Utils.ltrim;
35
import static com.scrivenvar.util.Utils.rtrim;
36
import com.vladsch.flexmark.ast.Link;
37
import com.vladsch.flexmark.ast.Node;
38
import java.nio.file.Path;
39
import java.util.regex.Matcher;
40
import java.util.regex.Pattern;
41
import javafx.beans.value.ObservableValue;
42
import javafx.scene.control.Dialog;
43
import javafx.scene.control.IndexRange;
44
import static javafx.scene.input.KeyCode.ENTER;
45
import javafx.scene.input.KeyEvent;
46
import javafx.stage.Window;
47
import org.fxmisc.richtext.StyleClassedTextArea;
48
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
49
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
50
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
51
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
52
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
53
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
54
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
55
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
56
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
57
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
58
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
59
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
60
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
61
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
62
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
63
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
64
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
65
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
66
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
67
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
68
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
69
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
70
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
71
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
72
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
73
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
74
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
75
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
76
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
77
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
78
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
79
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
80
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
81
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
82
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
83
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
84
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
85
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
86
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
87
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
88
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
89
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
90
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
91
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
92
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
93
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
94
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
95
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
96
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
97
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
98
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
99
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
100
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
101
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
102
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
103
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
104
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
105
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
106
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
107
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
108
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
109
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
110
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
111
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
112
import static com.scrivenvar.Constants.STYLESHEET_MARKDOWN;
113
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
114
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
115
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
116
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
117
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
118
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
119
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
120
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
121
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
122
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
123
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
124
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
125
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
126
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
127
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
128
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
129
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
130
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
131
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
132
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
133
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
134
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
135
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
136
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
137
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
138
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
139
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
140
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
141
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
142
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
143
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
144
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
145
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
146
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
147
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
148
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
149
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
150
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
151
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
152
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
153
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
154
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
155
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
156
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
157
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
158
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
159
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
160
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
161
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
162
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
163
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
164
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
165
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
166
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
167
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
168
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
169
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
170
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
171
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
172
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
173
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
174
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
175
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
176
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
177
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
178
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
179
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
180
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
181
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
182
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
183
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
184
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
185
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
186
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
187
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
188
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
189
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
190
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
191
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
192
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
193
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
194
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
195
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
196
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
197
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
198
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
199
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
200
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
201
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
202
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
203
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
204
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
205
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
206
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
207
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
208
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
209
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
210
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
211
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
212
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
213
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
214
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
215
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
216
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
217
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
218
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
219
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
220
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
221
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
222
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
223
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
224
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
225
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
226
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
227
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
228
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
229
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
230
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
231
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
232
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
233
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
234
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
235
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
236
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
237
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
238
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
239
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
240
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
241
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
242
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
243
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
244
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
245
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
246
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
247
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
248
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
249
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
250
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
251
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
252
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
253
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
254
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
255
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
256
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
257
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
258
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
259
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
260
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
261
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
262
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
263
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
264
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
265
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
266
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
267
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
268
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
269
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
270
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
271
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
272
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
273
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
274
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
275
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
276
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
277
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
278
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
279
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
280
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
281
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
282
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
283
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
284
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
285
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
286
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
287
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
288
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
289
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
290
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
291
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
292
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
293
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
294
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
295
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
296
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
297
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
298
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
299
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
300
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
301
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
302
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
303
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
304
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
305
306
/**
307
 * Markdown editor pane.
308
 *
309
 * @author Karl Tauber and White Magic Software, Ltd.
310
 */
311
public class MarkdownEditorPane extends EditorPane {
312
313
  private static final Pattern AUTO_INDENT_PATTERN = Pattern.compile(
314
    "(\\s*[*+-]\\s+|\\s*[0-9]+\\.\\s+|\\s+)(.*)" );
315
316
  public MarkdownEditorPane() {
317
    initEditor();
318
  }
319
320
  private void initEditor() {
321
    final StyleClassedTextArea textArea = getEditor();
322
323
    textArea.setWrapText( true );
324
    textArea.getStyleClass().add( "markdown-editor" );
325
    textArea.getStylesheets().add(STYLESHEET_MARKDOWN );
326
327
    addEventListener( keyPressed( ENTER ), this::enterPressed );
328
329
    // TODO: Wait for implementation that allows cutting lines, not paragraphs.
330
//    addEventListener( keyPressed( X, SHORTCUT_DOWN ), this::cutLine );
331
  }
332
333
  public ObservableValue<String> markdownProperty() {
334
    return getEditor().textProperty();
335
  }
336
337
  private void enterPressed( final KeyEvent e ) {
338
    final StyleClassedTextArea textArea = getEditor();
339
    final String currentLine = textArea.getText( textArea.getCurrentParagraph() );
340
    final Matcher matcher = AUTO_INDENT_PATTERN.matcher( currentLine );
341
342
    String newText = "\n";
343
344
    if( matcher.matches() ) {
345
      if( !matcher.group( 2 ).isEmpty() ) {
346
        // indent new line with same whitespace characters and list markers as current line
347
        newText = newText.concat( matcher.group( 1 ) );
348
      } else {
349
        // current line contains only whitespace characters and list markers
350
        // --> empty current line
351
        final int caretPosition = textArea.getCaretPosition();
352
        textArea.selectRange( caretPosition - currentLine.length(), caretPosition );
353
      }
354
    }
355
356
    textArea.replaceSelection( newText );
357
  }
358
359
  public void surroundSelection( final String leading, final String trailing ) {
360
    surroundSelection( leading, trailing, null );
361
  }
362
363
  public void surroundSelection( String leading, String trailing, final String hint ) {
364
    final StyleClassedTextArea textArea = getEditor();
365
366
    // Note: not using textArea.insertText() to insert leading and trailing
367
    // because this would add two changes to undo history
368
    final IndexRange selection = textArea.getSelection();
369
    int start = selection.getStart();
370
    int end = selection.getEnd();
371
372
    final String selectedText = textArea.getSelectedText();
373
374
    // remove leading and trailing whitespaces from selected text
375
    String trimmedSelectedText = selectedText.trim();
376
    if( trimmedSelectedText.length() < selectedText.length() ) {
377
      start += selectedText.indexOf( trimmedSelectedText );
378
      end = start + trimmedSelectedText.length();
379
    }
380
381
    // remove leading whitespaces from leading text if selection starts at zero
382
    if( start == 0 ) {
383
      leading = ltrim( leading );
384
    }
385
386
    // remove trailing whitespaces from trailing text if selection ends at text end
387
    if( end == textArea.getLength() ) {
388
      trailing = rtrim( trailing );
389
    }
390
391
    // remove leading line separators from leading text
392
    // if there are line separators before the selected text
393
    if( leading.startsWith( "\n" ) ) {
394
      for( int i = start - 1; i >= 0 && leading.startsWith( "\n" ); i-- ) {
395
        if( !"\n".equals( textArea.getText( i, i + 1 ) ) ) {
396
          break;
397
        }
398
        leading = leading.substring( 1 );
399
      }
400
    }
401
402
    // remove trailing line separators from trailing or leading text
403
    // if there are line separators after the selected text
404
    final boolean trailingIsEmpty = trailing.isEmpty();
405
    String str = trailingIsEmpty ? leading : trailing;
406
407
    if( str.endsWith( "\n" ) ) {
408
      final int length = textArea.getLength();
409
410
      for( int i = end; i < length && str.endsWith( "\n" ); i++ ) {
411
        if( !"\n".equals( textArea.getText( i, i + 1 ) ) ) {
412
          break;
413
        }
414
415
        str = str.substring( 0, str.length() - 1 );
416
      }
417
418
      if( trailingIsEmpty ) {
419
        leading = str;
420
      } else {
421
        trailing = str;
422
      }
423
    }
424
425
    int selStart = start + leading.length();
426
    int selEnd = end + leading.length();
427
428
    // insert hint text if selection is empty
429
    if( hint != null && trimmedSelectedText.isEmpty() ) {
430
      trimmedSelectedText = hint;
431
      selEnd = selStart + hint.length();
432
    }
433
434
    // prevent undo merging with previous text entered by user
435
    getUndoManager().preventMerge();
436
437
    // replace text and update selection
438
    textArea.replaceText( start, end, leading + trimmedSelectedText + trailing );
439
    textArea.selectRange( selStart, selEnd );
440
  }
441
442
  /**
443
   * Returns one of: selected text, word under cursor, or parsed hyperlink from
444
   * the markdown AST.
445
   *
446
   * @return
447
   */
448
  private HyperlinkModel getHyperlink() {
449
    final StyleClassedTextArea textArea = getEditor();
450
    final String selectedText = textArea.getSelectedText();
451
452
    // Get the current paragraph, convert to Markdown nodes.
453
    final MarkdownProcessor mp = new MarkdownProcessor( null );
454
    final int p = textArea.getCurrentParagraph();
455
    final String paragraph = textArea.getText( p );
456
    final Node node = mp.toNode( paragraph );
457
    final LinkVisitor visitor = new LinkVisitor( textArea.getCaretColumn() );
458
    final Link link = visitor.process( node );
459
460
    if( link != null ) {
461
      textArea.selectRange( p, link.getStartOffset(), p, link.getEndOffset() );
462
    }
463
464
    final HyperlinkModel model = createHyperlinkModel(
465
      link, selectedText, "https://website.com"
466
    );
467
468
    return model;
469
  }
470
471
  private HyperlinkModel createHyperlinkModel(
472
    final Link link, final String selection, final String url ) {
473
474
    return link == null
475
      ? new HyperlinkModel( selection, url )
476
      : new HyperlinkModel( link );
477
  }
478
479
  private Path getParentPath() {
480
    final Path parentPath = getPath();
481
    return (parentPath != null) ? parentPath.getParent() : null;
482
  }
483
484
  private Dialog<String> createLinkDialog() {
485
    return new LinkDialog( getWindow(), getHyperlink(), getParentPath() );
486
  }
487
488
  private Dialog<String> createImageDialog() {
489
    return new ImageDialog( getWindow(), getParentPath() );
490
  }
491
492
  private void insertObject( final Dialog<String> dialog ) {
493
    dialog.showAndWait().ifPresent( result -> {
494
      getEditor().replaceSelection( result );
495
    } );
496
  }
497
498
  public void insertLink() {
499
    insertObject( createLinkDialog() );
500
  }
501
502
  public void insertImage() {
503
    insertObject( createImageDialog() );
504
  }
505
506
  private Window getWindow() {
507
    return getScrollPane().getScene().getWindow();
508
  }
509
}
1510
A src/main/java/com/scrivenvar/predicates/files/FileTypePredicate.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.predicates.files;
29
30
import java.io.File;
31
import java.nio.file.FileSystems;
32
import java.nio.file.PathMatcher;
33
import java.util.Collection;
34
import java.util.function.Predicate;
35
36
/**
37
 * Responsible for testing whether a given path (to a file) matches one of the
38
 * filename extension patterns provided during construction.
39
 *
40
 * @see http://docs.oracle.com/javase/tutorial/essential/io/find.html
41
 *
42
 * @author White Magic Software, Ltd.
43
 */
44
public class FileTypePredicate implements Predicate<File> {
45
46
  private final PathMatcher matcher;
47
48
  /**
49
   * Constructs a new instance given a set of file extension globs.
50
   *
51
   * @param patterns Comma-separated list of globbed extensions including the
52
   * Kleene star (e.g., <code>*.md,*.markdown,*.txt</code>).
53
   */
54
  public FileTypePredicate( final String patterns ) {
55
    this.matcher = FileSystems.getDefault().getPathMatcher(
56
      "glob:**/{" + patterns + "}"
57
    );
58
  }
59
60
  /**
61
   * Constructs a new instance given a list of file extension globs, each must
62
   * include the Kleene star (a.k.a. asterisk).
63
   *
64
   * @param patterns Collection of globbed extensions.
65
   */
66
  public FileTypePredicate( final Collection<String> patterns ) {
67
    this( String.join( ",", patterns ) );
68
  }
69
70
  /**
71
   * Returns true if the file matches the patterns defined during construction.
72
   *
73
   * @param file The filename to match against the given glob patterns.
74
   *
75
   * @return false The filename does not match the glob patterns.
76
   */
77
  @Override
78
  public boolean test( final File file ) {
79
    return getMatcher().matches( file.toPath() );
80
  }
81
82
  private PathMatcher getMatcher() {
83
    return this.matcher;
84
  }
85
}
186
A src/main/java/com/scrivenvar/predicates/strings/ContainsPredicate.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.predicates.strings;
29
30
/**
31
 * Determines if one string contains another.
32
 *
33
 * @author White Magic Software, Ltd.
34
 */
35
public class ContainsPredicate extends StringPredicate {
36
37
  /**
38
   * Calls the superclass to construct the instance.
39
   *
40
   * @param comparate Not null.
41
   */
42
  public ContainsPredicate( final String comparate ) {
43
    super( comparate );
44
  }
45
46
  /**
47
   * Answers whether the given strings match each other. What match means will
48
   * depend on user preferences. The empty condition is required to return the
49
   * first node in a list of child nodes when the user has not yet selected a
50
   * node.
51
   *
52
   * @param comparator The string to compare against the comparate.
53
   *
54
   * @return true if s1 and s2 are a match according to some criteria,or s2 is
55
   * empty.
56
   */
57
  @Override
58
  public boolean test( final String comparator ) {
59
    final String comparate = getComparate().toLowerCase();
60
    return comparator.contains( comparate.toLowerCase() )
61
      || comparate.isEmpty();
62
  }
63
}
164
A src/main/java/com/scrivenvar/predicates/strings/EqualPredicate.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.predicates.strings;
29
30
/**
31
 * Determines if two strings are equal.
32
 *
33
 * @author White Magic Software, Ltd.
34
 */
35
public class EqualPredicate extends StringPredicate {
36
37
  /**
38
   * Calls the superclass to construct the instance.
39
   *
40
   * @param comparate Not null.
41
   */
42
  public EqualPredicate( final String comparate ) {
43
    super( comparate );
44
  }
45
46
  /**
47
   * Compares two strings.
48
   *
49
   * @param comparator A non-null string, possibly empty.
50
   *
51
   * @return true The strings are equal, ignoring case.
52
   */
53
  @Override
54
  public boolean test( final String comparator ) {
55
    return comparator.equalsIgnoreCase( getComparate() );
56
  }
57
}
158
A src/main/java/com/scrivenvar/predicates/strings/StartsPredicate.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.predicates.strings;
29
30
/**
31
 * Determines if a string starts with another.
32
 *
33
 * @author White Magic Software, Ltd.
34
 */
35
public class StartsPredicate extends StringPredicate {
36
37
  /**
38
   * Constructs a new instance using a comparate that will be compared with
39
   * the comparator during the test.
40
   *
41
   * @param comparate The string to compare against the comparator.
42
   */
43
  public StartsPredicate( final String comparate ) {
44
    super( comparate );
45
  }
46
47
  /**
48
   * Compares two strings.
49
   *
50
   * @param comparator A non-null string, possibly empty.
51
   *
52
   * @return true The comparator starts with the comparate, ignoring case.
53
   */
54
  @Override
55
  public boolean test( final String comparator ) {
56
    final String comparate = getComparate().toLowerCase();
57
    return comparator.startsWith( comparate.toLowerCase() );
58
  }
59
}
160
A src/main/java/com/scrivenvar/predicates/strings/StringPredicate.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.predicates.strings;
29
30
import java.util.function.Predicate;
31
32
/**
33
 * General predicate for different types of string comparisons.
34
 *
35
 * @author White Magic Software, Ltd.
36
 */
37
public abstract class StringPredicate implements Predicate<String> {
38
39
  private final String comparate;
40
41
  public StringPredicate( final String comparate ) {
42
    this.comparate = comparate;
43
  }
44
45
  protected String getComparate() {
46
    return this.comparate;
47
  }
48
}
149
A src/main/java/com/scrivenvar/preview/HTMLPreviewPane.java
1
/*
2
 * Copyright 2016 Karl Tauber and White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.preview;
29
30
import static com.scrivenvar.Constants.CARET_POSITION_BASE;
31
import static com.scrivenvar.Constants.STYLESHEET_PREVIEW;
32
import java.nio.file.Path;
33
import javafx.beans.value.ObservableValue;
34
import javafx.concurrent.Worker.State;
35
import static javafx.concurrent.Worker.State.SUCCEEDED;
36
import javafx.scene.Node;
37
import javafx.scene.layout.Pane;
38
import javafx.scene.web.WebEngine;
39
import javafx.scene.web.WebView;
40
41
/**
42
 * HTML preview pane is responsible for rendering an HTML document.
43
 *
44
 * @author Karl Tauber and White Magic Software, Ltd.
45
 */
46
public final class HTMLPreviewPane extends Pane {
47
48
  private final WebView webView = new WebView();
49
  private Path path;
50
51
  /**
52
   * Creates a new preview pane that can scroll to the caret position within the
53
   * document.
54
   */
55
  public HTMLPreviewPane() {
56
    initListeners();
57
    initTraversal();
58
  }
59
60
  /**
61
   * Initializes observers for document changes. When the document is reloaded
62
   * with new HTML, this triggers a scroll event that repositions the document
63
   * to the injected caret (that corresponds with the position in the text
64
   * editor).
65
   */
66
  private void initListeners() {
67
    // Scrolls to the caret after the content has been loaded.
68
    getEngine().getLoadWorker().stateProperty().addListener(
69
      (ObservableValue<? extends State> observable,
70
        final State oldValue, final State newValue) -> {
71
        if( newValue == SUCCEEDED ) {
72
          scrollToCaret();
73
        }
74
      } );
75
  }
76
77
  /**
78
   * Ensures images can be found relative to the document.
79
   *
80
   * @return The base path element to use for the document, or the empty string
81
   * if no path has been set, yet.
82
   */
83
  private String getBase() {
84
    final Path basePath = getPath();
85
86
    return basePath == null
87
      ? ""
88
      : ("<base href='" + basePath.getParent().toUri().toString() + "'>");
89
  }
90
91
  /**
92
   * Updates the internal HTML source, loads it into the preview pane, then
93
   * scrolls to the caret position.
94
   *
95
   * @param html The new HTML document to display.
96
   */
97
  public void update( final String html ) {
98
    getEngine().loadContent(
99
      "<!DOCTYPE html>"
100
      + "<html>"
101
      + "<head>"
102
      + "<link rel='stylesheet' href='" + getClass().getResource( STYLESHEET_PREVIEW ) + "'>"
103
      + getBase()
104
      + "</head>"
105
      + "<body>"
106
      + html
107
      + "</body>"
108
      + "</html>" );
109
  }
110
111
  /**
112
   * Clears out the HTML content from the preview.
113
   */
114
  public void clear() {
115
    update( "" );
116
  }
117
118
  /**
119
   * Scrolls to the caret position in the document.
120
   */
121
  private void scrollToCaret() {
122
    execute( getScrollScript() );
123
  }
124
125
  /**
126
   * Returns the JavaScript used to scroll the WebView pane.
127
   *
128
   * @return A script that tries to center the view port on the CARET POSITION.
129
   */
130
  private String getScrollScript() {
131
    return ""
132
      + "var e = document.getElementById('" + CARET_POSITION_BASE + "');"
133
      + "if( e != null ) { "
134
      + "  Element.prototype.topOffset = function () {"
135
      + "    return this.offsetTop + (this.offsetParent ? this.offsetParent.topOffset() : 0);"
136
      + "  };"
137
      + "  window.scrollTo( 0, e.topOffset() - (window.innerHeight / 2 ) );"
138
      + "}";
139
  }
140
141
  /**
142
   * Prevent tabbing into the preview pane.
143
   */
144
  private void initTraversal() {
145
    getWebView().setFocusTraversable( false );
146
  }
147
148
  private Object execute( final String script ) {
149
    return getEngine().executeScript( script );
150
  }
151
152
  private WebEngine getEngine() {
153
    return getWebView().getEngine();
154
  }
155
156
  private WebView getWebView() {
157
    return this.webView;
158
  }
159
160
  private Path getPath() {
161
    return this.path;
162
  }
163
164
  public void setPath( final Path path ) {
165
    this.path = path;
166
  }
167
168
  /**
169
   * Content to embed in a panel.
170
   *
171
   * @return The content to display to the user.
172
   */
173
  public Node getNode() {
174
    return getWebView();
175
  }
176
}
1177
A src/main/java/com/scrivenvar/processors/AbstractProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
/**
31
 * Responsible for transforming a document through a variety of chained
32
 * handlers. If there are conditions where this handler should not process the
33
 * entire chain, create a second handler, or split the chain into reusable
34
 * sub-chains.
35
 *
36
 * @author White Magic Software, Ltd.
37
 * @param <T> The type of object to process.
38
 */
39
public abstract class AbstractProcessor<T> implements Processor<T> {
40
41
  /**
42
   * Used while processing the entire chain; null to signify no more links.
43
   */
44
  private final Processor<T> next;
45
46
  /**
47
   * Constructs a succession without a successor (i.e., next is null).
48
   */
49
  protected AbstractProcessor() {
50
    this( null );
51
  }
52
53
  /**
54
   * Constructs a new default handler with a given successor.
55
   *
56
   * @param successor Use null to indicate last link in the chain.
57
   */
58
  public AbstractProcessor( final Processor<T> successor ) {
59
    this.next = successor;
60
  }
61
62
  /**
63
   * Processes links in the chain while there are successors and valid data to
64
   * process.
65
   *
66
   * @param t The object to process.
67
   */
68
  @Override
69
  public synchronized void processChain( T t ) {
70
    Processor<T> handler = this;
71
72
    while( handler != null && t != null ) {
73
      t = handler.processLink( t );
74
      handler = handler.next();
75
    }
76
  }
77
78
  @Override
79
  public Processor<T> next() {
80
    return this.next;
81
  }
82
}
183
A src/main/java/com/scrivenvar/processors/HTMLPreviewProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
import com.scrivenvar.preview.HTMLPreviewPane;
31
32
/**
33
 * Responsible for notifying the HTMLPreviewPane when the succession chain has
34
 * updated. This decouples knowledge of changes to the editor panel from the
35
 * HTML preview panel as well as any processing that takes place before the
36
 * final HTML preview is rendered. This should be the last link in the processor
37
 * chain.
38
 *
39
 * @author White Magic Software, Ltd.
40
 */
41
public class HTMLPreviewProcessor extends AbstractProcessor<String> {
42
43
  private HTMLPreviewPane htmlPreviewPane;
44
45
  /**
46
   * Constructs the end of a processing chain.
47
   *
48
   * @param htmlPreviewPane The pane to update with the post-processed document.
49
   */
50
  public HTMLPreviewProcessor( final HTMLPreviewPane htmlPreviewPane ) {
51
    super( null );
52
    setHtmlPreviewPane( htmlPreviewPane );
53
  }
54
55
  /**
56
   * Update the preview panel using HTML from the succession chain.
57
   *
58
   * @param html The document content to render in the preview pane. The HTML
59
   * should not contain a doctype, head, or body tag, only content to render
60
   * within the body.
61
   *
62
   * @return null
63
   */
64
  @Override
65
  public String processLink( final String html ) {
66
    getHtmlPreviewPane().update( html );
67
68
    // No more processing required.
69
    return null;
70
  }
71
72
  private HTMLPreviewPane getHtmlPreviewPane() {
73
    return this.htmlPreviewPane;
74
  }
75
76
  private void setHtmlPreviewPane( final HTMLPreviewPane htmlPreviewPane ) {
77
    this.htmlPreviewPane = htmlPreviewPane;
78
  }
79
}
180
A src/main/java/com/scrivenvar/processors/MarkdownCaretInsertionProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
import static com.scrivenvar.Constants.CARET_POSITION_MD;
31
import static java.lang.Character.isLetter;
32
33
/**
34
 * Responsible for inserting the magic CARET POSITION into the markdown so that,
35
 * upon rendering into HTML, the HTML pane can scroll to the correct position
36
 * (relative to the caret position in the editor).
37
 *
38
 * @author White Magic Software, Ltd.
39
 */
40
public class MarkdownCaretInsertionProcessor extends AbstractProcessor<String> {
41
42
  private final int caretPosition;
43
44
  /**
45
   * Constructs a processor capable of inserting a caret marker into Markdown.
46
   *
47
   * @param processor The next processor in the chain.
48
   * @param position The caret's current position in the text, cannot be null.
49
   */
50
  public MarkdownCaretInsertionProcessor(
51
    final Processor<String> processor, final int position ) {
52
    super( processor );
53
    this.caretPosition = position;
54
  }
55
56
  /**
57
   * Changes the text to insert a "caret" at the caret position. This will
58
   * insert the unique key of Constants.MD_CARET_POSITION into the document.
59
   *
60
   * @param t The document text to process.
61
   *
62
   * @return The document text with the Markdown caret text inserted at the
63
   * caret position (given at construction time).
64
   */
65
  @Override
66
  public String processLink( final String t ) {
67
    int offset = getCaretPosition();
68
    final int length = t.length();
69
70
    // Insert the caret at the closest non-Markdown delimiter (i.e., the 
71
    // closest character from the caret position forward).
72
    while( offset < length && !isLetter( t.charAt( offset ) ) ) {
73
      offset++;
74
    }
75
76
    // TODO: Ensure that the caret position is outside of an element, 
77
    // so that a caret inserted in the image doesn't corrupt it. Such as:
78
    //
79
    // ![Screenshot](images/scr|eenshot.png)
80
    // Insert the caret position into the Markdown text, but don't interfere
81
    // with the Markdown iteself.
82
    return new StringBuilder( t ).replace(
83
      offset, offset, CARET_POSITION_MD ).toString();
84
  }
85
86
  /**
87
   * Returns the editor's caret position.
88
   *
89
   * @return Where the user has positioned the caret.
90
   */
91
  private int getCaretPosition() {
92
    return this.caretPosition;
93
  }
94
}
195
A src/main/java/com/scrivenvar/processors/MarkdownCaretReplacementProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
import static com.scrivenvar.Constants.CARET_POSITION_HTML;
31
import static com.scrivenvar.Constants.CARET_POSITION_MD;
32
33
/**
34
 * Responsible for replacing the caret position marker with an HTML element
35
 * suitable to use as a reference for scrolling a view port.
36
 *
37
 * @author White Magic Software, Ltd.
38
 */
39
public class MarkdownCaretReplacementProcessor extends AbstractProcessor<String> {
40
  private static final int INDEX_NOT_FOUND = -1;
41
42
  public MarkdownCaretReplacementProcessor( final Processor<String> processor ) {
43
    super( processor );
44
  }
45
46
  /**
47
   * Replaces each MD_CARET_POSITION with an HTML element that has an id
48
   * attribute of CARET_POSITION. This should only replace one item.
49
   *
50
   * @param t The text that contains
51
   *
52
   * @return
53
   */
54
  @Override
55
  public String processLink( final String t ) {
56
    return replace(t, CARET_POSITION_MD, CARET_POSITION_HTML );
57
  }
58
59
  /**
60
   * Replaces the needle with thread in the given haystack. Based on Apache
61
   * Commons 3 StringUtils.replace method. Should be faster than
62
   * String.replace, which performs a little regex under the hood.
63
   *
64
   * @param haystack Search this string for the needle, must not be null.
65
   * @param needle The text to find in the haystack.
66
   * @param thread Replace the needle with this text, if the needle is found.
67
   *
68
   * @return The haystack with the first instance of needle replaced with
69
   * thread.
70
   */
71
  private static String replace(
72
    final String haystack, final String needle, final String thread ) {
73
74
    final int end = haystack.indexOf( needle, 0 );
75
76
    if( end == INDEX_NOT_FOUND ) {
77
      return haystack;
78
    }
79
80
    int start = 0;
81
    final int needleLength = needle.length();
82
83
    int increase = thread.length() - needleLength;
84
    increase = (increase < 0 ? 0 : increase);
85
    final StringBuilder buffer = new StringBuilder( haystack.length() + increase );
86
87
    if( end != INDEX_NOT_FOUND ) {
88
      buffer.append( haystack.substring( start, end ) ).append( thread );
89
      start = end + needleLength;
90
    }
91
92
    return buffer.append( haystack.substring( start ) ).toString();
93
  }
94
}
195
A src/main/java/com/scrivenvar/processors/MarkdownProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
import com.vladsch.flexmark.Extension;
31
import com.vladsch.flexmark.ast.Node;
32
import com.vladsch.flexmark.ext.gfm.tables.TablesExtension;
33
import com.vladsch.flexmark.html.HtmlRenderer;
34
import com.vladsch.flexmark.parser.Parser;
35
import java.util.ArrayList;
36
import java.util.List;
37
38
39
/**
40
 * Responsible for parsing a Markdown document and rendering it as HTML.
41
 *
42
 * @author White Magic Software, Ltd.
43
 */
44
public class MarkdownProcessor extends AbstractProcessor<String> {
45
46
  private List<Extension> extensions;
47
48
  /**
49
   * Constructs a new Markdown processor that can create HTML documents.
50
   *
51
   * @param successor Usually the HTML Preview Processor.
52
   */
53
  public MarkdownProcessor( final Processor<String> successor ) {
54
    super( successor );
55
  }
56
57
  /**
58
   * Converts the given Markdown string into HTML, without the doctype, html,
59
   * head, and body tags.
60
   *
61
   * @param markdown The string to convert from Markdown to HTML.
62
   *
63
   * @return The HTML representation of the Markdown document.
64
   */
65
  @Override
66
  public String processLink( final String markdown ) {
67
    return toHtml( markdown );
68
  }
69
70
  /**
71
   * Returns the AST in the form of a node for the given markdown document. This
72
   * can be used, for example, to determine if a hyperlink exists inside of a
73
   * paragraph.
74
   *
75
   * @param markdown The markdown to convert into an AST.
76
   *
77
   * @return The markdown AST for the given text (usually a paragraph).
78
   */
79
  public Node toNode( final String markdown ) {
80
    return parse( markdown );
81
  }
82
83
  /**
84
   * Helper method to create an AST given some markdown.
85
   *
86
   * @param markdown The markdown to parse.
87
   *
88
   * @return The root node of the markdown tree.
89
   */
90
  private Node parse( final String markdown ) {
91
    return createParser().parse( markdown );
92
  }
93
94
  /**
95
   * Converts a string of markdown into HTML.
96
   *
97
   * @param markdown The markdown text to convert to HTML, must not be null.
98
   *
99
   * @return The markdown rendered as an HTML document.
100
   */
101
  private String toHtml( final String markdown ) {
102
    return createRenderer().render( parse( markdown ) );
103
  }
104
105
  /**
106
   * Returns the list of extensions to use when parsing and rendering Markdown
107
   * into HTML.
108
   *
109
   * @return A non-null list of Markdown extensions.
110
   */
111
  private synchronized List<Extension> getExtensions() {
112
    if( this.extensions == null ) {
113
      this.extensions = createExtensions();
114
    }
115
116
    return this.extensions;
117
  }
118
119
  /**
120
   * Creates a list that includes a TablesExtension. Subclasses may override
121
   * this method to insert more extensions, or remove the table extension.
122
   *
123
   * @return A list with an extension for parsing and rendering tables.
124
   */
125
  protected List<Extension> createExtensions() {
126
    final List<Extension> result = new ArrayList<>();
127
    result.add( TablesExtension.create() );
128
    return result;
129
  }
130
131
  /**
132
   * Creates the Markdown document processor.
133
   *
134
   * @return A Parser that can build an abstract syntax tree.
135
   */
136
  private Parser createParser() {
137
    return Parser.builder().extensions( getExtensions() ).build();
138
  }
139
140
  /**
141
   * Creates the HTML document renderer.
142
   *
143
   * @return A renderer that can convert a Markdown AST to HTML.
144
   */
145
  private HtmlRenderer createRenderer() {
146
    return HtmlRenderer.builder().extensions( getExtensions() ).build();
147
  }
148
}
1149
A src/main/java/com/scrivenvar/processors/Processor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
/**
31
 * Responsible for processing documents from one known format to another.
32
 *
33
 * @author White Magic Software, Ltd.
34
 * @param <T> The type of processor to create.
35
 */
36
public interface Processor<T> {
37
  
38
  /**
39
   * Provided so that the chain can be invoked from any link using a given
40
   * value. This should be called automatically by a superclass so that
41
   * the links in the chain need only implement the processLink method.
42
   * 
43
   * @param t The value to pass along to each link in the chain.
44
   */
45
  public void processChain( T t );
46
47
  /**
48
   * Processes the given content providing a transformation from one document
49
   * format into another. For example, this could convert from XML to text using
50
   * an XSLT processor, or from markdown to HTML.
51
   *
52
   * @param t The type of object to process.
53
   *
54
   * @return The post-processed document, or null if processing should stop.
55
   */
56
  public T processLink( T t );
57
58
  /**
59
   * Adds a document processor to call after this processor finishes processing
60
   * the document given to the process method.
61
   *
62
   * @return The processor that should transform the document after this
63
   * instance has finished processing.
64
   */
65
  public Processor<T> next();
66
}
167
A src/main/java/com/scrivenvar/processors/VariableProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
import com.scrivenvar.processors.text.TextReplacementFactory;
31
import com.scrivenvar.processors.text.TextReplacer;
32
import java.util.Map;
33
34
/**
35
 * Processes variables in the document and inserts their values into the
36
 * post-processed text.
37
 *
38
 * @author White Magic Software, Ltd.
39
 */
40
public class VariableProcessor extends AbstractProcessor<String> {
41
42
  private Map<String, String> definitions;
43
44
  /**
45
   * Constructs a new Markdown processor that can create HTML documents.
46
   *
47
   * @param successor Usually the HTML Preview Processor.
48
   */
49
  private VariableProcessor( final Processor<String> successor ) {
50
    super( successor );
51
  }
52
53
  public VariableProcessor(
54
    final Processor<String> successor,
55
    final Map<String, String> map ) {
56
    this( successor );
57
    setDefinitions( map );
58
  }
59
60
  /**
61
   *
62
   * @param text The document text that includes variables that should be
63
   * replaced with values when rendered as HTML.
64
   *
65
   * @return The text with all variables replaced.
66
   */
67
  @Override
68
  public String processLink( final String text ) {
69
    final TextReplacer tr = TextReplacementFactory.getTextReplacer( text.length() );
70
71
    return tr.replace( text, getDefinitions() );
72
  }
73
74
  private Map<String, String> getDefinitions() {
75
    return this.definitions;
76
  }
77
78
  private void setDefinitions( final Map<String, String> definitions ) {
79
    this.definitions = definitions;
80
  }
81
}
182
A src/main/java/com/scrivenvar/processors/text/AbstractTextReplacer.java
1
/*
2
 * The MIT License
3
 *
4
 * Copyright 2016 .
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in
14
 * all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
 * THE SOFTWARE.
23
 */
24
package com.scrivenvar.processors.text;
25
26
import java.util.Map;
27
28
/**
29
 * Responsible for common behaviour across all text replacer implementations.
30
 *
31
 * @author White Magic Software, Ltd.
32
 */
33
public abstract class AbstractTextReplacer implements TextReplacer {
34
35
  /**
36
   * Default (empty) constructor.
37
   */
38
  protected AbstractTextReplacer() {
39
  }
40
41
  protected String[] keys( final Map<String, String> map ) {
42
    return map.keySet().toArray( new String[ map.size() ] );
43
  }
44
45
  protected String[] values( final Map<String, String> map ) {
46
    return map.values().toArray( new String[ map.size() ] );
47
  }
48
}
149
A src/main/java/com/scrivenvar/processors/text/AhoCorasickReplacer.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors.text;
29
30
import java.util.Map;
31
import org.ahocorasick.trie.Emit;
32
import org.ahocorasick.trie.Trie.TrieBuilder;
33
import static org.ahocorasick.trie.Trie.builder;
34
35
/**
36
 * Replaces text using an Aho-Corasick algorithm.
37
 *
38
 * @author White Magic Software, Ltd.
39
 */
40
public class AhoCorasickReplacer extends AbstractTextReplacer {
41
42
  /**
43
   * Default (empty) constructor.
44
   */
45
  protected AhoCorasickReplacer() {
46
  }
47
48
  @Override
49
  public String replace( final String text, final Map<String, String> map ) {
50
    // Create a buffer sufficiently large that re-allocations are minimized.
51
    final StringBuilder sb = new StringBuilder( (int)(text.length() * 1.25) );
52
53
    // The TrieBuilder should only match whole words and ignore overlaps (there
54
    // shouldn't be any).
55
    final TrieBuilder builder = builder().onlyWholeWords().removeOverlaps();
56
57
    for( final String key : keys( map ) ) {
58
      builder.addKeyword( key );
59
    }
60
61
    int index = 0;
62
63
    for( final Emit emit : builder.build().parseText( text ) ) {
64
      sb.append( text.substring( index, emit.getStart() ) );
65
      sb.append( map.get( emit.getKeyword() ) );
66
      index = emit.getEnd() + 1;
67
    }
68
69
    // Add the remainder of the string (contains no more matches).
70
    sb.append( text.substring( index ) );
71
72
    return sb.toString();
73
  }
74
}
175
A src/main/java/com/scrivenvar/processors/text/StringUtilsReplacer.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors.text;
29
30
import java.util.Map;
31
import static org.apache.commons.lang.StringUtils.replaceEach;
32
33
/**
34
 * Replaces text using Apache's StringUtils.replaceEach method.
35
 *
36
 * @author White Magic Software, Ltd.
37
 */
38
public class StringUtilsReplacer extends AbstractTextReplacer {
39
40
  /**
41
   * Default (empty) constructor.
42
   */
43
  protected StringUtilsReplacer() {
44
  }
45
46
  @Override
47
  public String replace( final String text, final Map<String, String> map ) {
48
    return replaceEach( text, keys( map ), values( map ) );
49
  }
50
}
151
A src/main/java/com/scrivenvar/processors/text/TextReplacementFactory.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors.text;
29
30
/**
31
 * Used to generate a class capable of efficiently replacing variable
32
 * definitions with their values.
33
 *
34
 * @author White Magic Software, Ltd.
35
 */
36
public class TextReplacementFactory {
37
38
  /**
39
   * Returns a text search/replacement instance that is reasonably optimal for
40
   * the given length of text.
41
   *
42
   * @param length The length of text that requires some search and replacing.
43
   *
44
   * @return A class that can search and replace text with utmost expediency.
45
   */
46
  public static TextReplacer getTextReplacer( final int length ) {
47
    // After about 1,500 characters, the StringUtils implementation is less
48
    // performant than the Aho-Corsick implementation.
49
    //
50
    // Ssee http://stackoverflow.com/a/40836618/59087
51
    return length < 1500
52
      ? new StringUtilsReplacer()
53
      : new AhoCorasickReplacer();
54
  }
55
}
156
A src/main/java/com/scrivenvar/processors/text/TextReplacer.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors.text;
29
30
import java.util.Map;
31
32
/**
33
 * Defines the ability to replace text given a set of keys and values.
34
 *
35
 * @author White Magic Software, Ltd.
36
 */
37
public interface TextReplacer {
38
39
  /**
40
   * Searches through the given text for any of the keys given in the map and
41
   * replaces the keys that appear in the text with the key's corresponding
42
   * value.
43
   *
44
   * @param text The text that contains zero or more keys.
45
   * @param map The set of keys mapped to replacement values.
46
   *
47
   * @return The given text with all keys replaced with corresponding values.
48
   */
49
  public String replace( String text, Map<String, String> map );
50
}
151
A src/main/java/com/scrivenvar/service/Configuration.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service;
29
30
/**
31
 *
32
 * @author White Magic Software, Ltd.
33
 */
34
public interface Configuration extends Service {
35
36
  public Settings getSettings();
37
38
  public Options getOptions();
39
}
140
A src/main/java/com/scrivenvar/service/Options.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service;
29
30
import java.util.prefs.Preferences;
31
32
/**
33
 * Responsible for persistent options.
34
 *
35
 * @author White Magic Software, Ltd.
36
 */
37
public interface Options {
38
39
  public Preferences getState();
40
  
41
  /**
42
   * Stores the key and value into the user preferences to be loaded the next
43
   * time the application is launched.
44
   *
45
   * @param key Name of the key to persist along with its value.
46
   * @param value Value to associate with the key.
47
   */
48
  public void put( String key, String value );
49
50
  /**
51
   * Retrieves the value for a key in the user preferences.
52
   *
53
   * @param key Retrieve the value of this key.
54
   * @param defaultValue The value to return in the event that the given key has
55
   * no associated value.
56
   *
57
   * @return The value associated with the key.
58
   */
59
  public String get( String key, String defaultValue );
60
}
161
A src/main/java/com/scrivenvar/service/Service.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service;
29
30
/**
31
 * All services inherit from this one.
32
 *
33
 * @author White Magic Software, Ltd.
34
 */
35
public interface Service {
36
}
137
A src/main/java/com/scrivenvar/service/Settings.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service;
29
30
import java.util.Iterator;
31
import java.util.List;
32
33
/**
34
 * Defines how settings and options can be retrieved.
35
 *
36
 * @author White Magic Software, Ltd.
37
 */
38
public interface Settings extends Service {
39
40
  /**
41
   * Returns a setting property or its default value.
42
   *
43
   * @param property The property key name to obtain its value.
44
   * @param defaultValue The default value to return iff the property cannot be
45
   * found.
46
   *
47
   * @return The property value for the given property key.
48
   */
49
  public String getSetting( String property, String defaultValue );
50
51
  /**
52
   * Returns a setting property or its default value.
53
   *
54
   * @param property The property key name to obtain its value.
55
   * @param defaultValue The default value to return iff the property cannot be
56
   * found.
57
   *
58
   * @return The property value for the given property key.
59
   */
60
  public int getSetting( String property, int defaultValue );
61
62
  /**
63
   * Returns a setting property or its default value.
64
   *
65
   * @param property The property key name to obtain its value.
66
   * @param defaults The default values to return iff the property cannot be
67
   * found.
68
   *
69
   * @return The property values for the given property key.
70
   */
71
  public List<Object> getSettingList( String property, List<String> defaults );
72
73
  /**
74
   * Returns a list of property names that begin with the given prefix. The
75
   * prefix is included in any matching results. This will return keys that
76
   * either match the prefix or start with the prefix followed by a dot ('.').
77
   * For example, a prefix value of <code>the.property.name</code> will likely
78
   * return the expected results, but <code>the.property.name.</code> (note the
79
   * extraneous period) will probably not.
80
   *
81
   * @param prefix The prefix to compare against each property name.
82
   *
83
   * @return The list of property names that have the given prefix.
84
   */
85
  public Iterator<String> getKeys( final String prefix );
86
87
  /**
88
   * Convert the generic list of property objects into strings.
89
   *
90
   * @param property The property value to coerce.
91
   * @param defaults The defaults values to use should the property be unset.
92
   *
93
   * @return The list of properties coerced from objects to strings.
94
   */
95
  public List<String> getStringSettingList( String property, List<String> defaults );
96
97
  /**
98
   * Converts the generic list of property objects into strings.
99
   *
100
   * @param property The property value to coerce.
101
   *
102
   * @return The list of properties coerced from objects to strings.
103
   */
104
  public List<String> getStringSettingList( String property );
105
}
1106
A src/main/java/com/scrivenvar/service/events/AlertMessage.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service.events;
29
30
/**
31
 *
32
 * @author White Magic Software, Ltd.
33
 */
34
public interface AlertMessage {
35
36
  /**
37
   * Dialog box title.
38
   *
39
   * @return A non-null string to use as the title for the dialog.
40
   */
41
  public String getTitle();
42
43
  /**
44
   * Dialog box message content.
45
   *
46
   * @return A non-null string to use as the alert message for the dialog.
47
   */
48
  public String getContent();
49
}
150
A src/main/java/com/scrivenvar/service/events/AlertService.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service.events;
29
30
import javafx.scene.control.Alert;
31
import javafx.scene.control.ButtonType;
32
import javafx.stage.Window;
33
34
/**
35
 * Provides the application with a uniform way to create alert dialogs.
36
 *
37
 * @author White Magic Software, Ltd.
38
 */
39
public interface AlertService {
40
  public static final ButtonType YES = ButtonType.YES;
41
  public static final ButtonType NO = ButtonType.NO;
42
  public static final ButtonType CANCEL = ButtonType.CANCEL;
43
44
  /**
45
   * Called to set the window used as the parent for the alert dialogs.
46
   *
47
   * @param window
48
   */
49
  public void setWindow( Window window );
50
51
  /**
52
   * Constructs a default alert message text for a modal alert dialog.
53
   *
54
   * @param title The dialog box message title.
55
   * @param message The dialog box message content (needs formatting).
56
   * @param args The arguments to the message content that must be formatted.
57
   *
58
   * @return The message suitable for building a modal alert dialog.
59
   */
60
  public AlertMessage createAlertMessage(
61
    String title,
62
    String message,
63
    Object... args );
64
65
  /**
66
   * Creates an alert of alert type error with a message showing the cause of
67
   * the error.
68
   *
69
   * @param alertMessage The error message, title, and possibly more details.
70
   *
71
   * @return A modal alert dialog box ready to display using showAndWait.
72
   */
73
  public Alert createAlertError( AlertMessage alertMessage );
74
75
  /**
76
   * Creates an alert of alert type confirmation with Yes/No/Cancel buttons.
77
   *
78
   * @param alertMessage The message, title, and possibly more details.
79
   *
80
   * @return A modal alert dialog box ready to display using showAndWait.
81
   */
82
  public Alert createAlertConfirmation( AlertMessage alertMessage );
83
}
184
A src/main/java/com/scrivenvar/service/events/impl/ButtonOrderPane.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service.events.impl;
29
30
import com.scrivenvar.Services;
31
import com.scrivenvar.service.Settings;
32
import javafx.scene.Node;
33
import javafx.scene.control.ButtonBar;
34
import static javafx.scene.control.ButtonBar.BUTTON_ORDER_WINDOWS;
35
import javafx.scene.control.DialogPane;
36
37
/**
38
 * Ensures a consistent button order for alert dialogs across platforms (because
39
 * the default button order on Linux defies all logic).
40
 *
41
 * @author White Magic Software, Ltd.
42
 */
43
public class ButtonOrderPane extends DialogPane {
44
45
  @Override
46
  protected Node createButtonBar() {
47
    final ButtonBar node = (ButtonBar)super.createButtonBar();
48
    node.setButtonOrder( getButtonOrder() );
49
    return node;
50
  }
51
52
  private String getButtonOrder() {
53
    return getSetting( "dialog.alert.button.order.windows", BUTTON_ORDER_WINDOWS );
54
  }
55
56
  private String getSetting( final String key, final String defaultValue ) {
57
    return getSettings().getSetting( key, defaultValue );
58
  }
59
60
  private Settings getSettings() {
61
    return Services.load( Settings.class );
62
  }
63
}
164
A src/main/java/com/scrivenvar/service/events/impl/DefaultAlertMessage.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service.events.impl;
29
30
import com.scrivenvar.service.events.AlertMessage;
31
import java.text.MessageFormat;
32
33
/**
34
 *
35
 * @author White Magic Software, Ltd.
36
 */
37
public class DefaultAlertMessage implements AlertMessage {
38
39
  private final String title;
40
  private final String content;
41
42
  /**
43
   * Constructs a default alert message text for an alert modal dialog.
44
   * 
45
   * @param title The dialog box message title.
46
   * @param message The dialog box message content (needs formatting).
47
   * @param args The arguments to the message content that must be formatted.
48
   */
49
  public DefaultAlertMessage(
50
    final String title,
51
    final String message,
52
    final Object... args ) {
53
    this.title = title;
54
    this.content = MessageFormat.format( message, args );
55
  }
56
57
  @Override
58
  public String getTitle() {
59
    return this.title;
60
  }
61
62
  @Override
63
  public String getContent() {
64
    return this.content;
65
  }
66
}
167
A src/main/java/com/scrivenvar/service/events/impl/DefaultAlertService.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service.events.impl;
29
30
import com.scrivenvar.service.events.AlertMessage;
31
import com.scrivenvar.service.events.AlertService;
32
import javafx.scene.control.Alert;
33
import javafx.scene.control.Alert.AlertType;
34
import static javafx.scene.control.Alert.AlertType.CONFIRMATION;
35
import static javafx.scene.control.Alert.AlertType.ERROR;
36
import javafx.stage.Window;
37
38
/**
39
 * Provides the ability to create error alert boxes.
40
 *
41
 * @author White Magic Software, Ltd.
42
 */
43
public final class DefaultAlertService implements AlertService {
44
45
  private Window window;
46
47
  public DefaultAlertService() {
48
  }
49
50
  public DefaultAlertService( final Window window ) {
51
    this.window = window;
52
  }
53
54
  @Override
55
  public AlertMessage createAlertMessage(
56
    final String title,
57
    final String message,
58
    final Object... args ) {
59
    return new DefaultAlertMessage( title, message, args );
60
  }
61
62
  private Alert createAlertDialog(
63
    final AlertType alertType,
64
    final AlertMessage message ) {
65
66
    final Alert alert = new Alert( alertType );
67
68
    alert.setDialogPane( new ButtonOrderPane() );
69
    alert.setTitle( message.getTitle() );
70
    alert.setHeaderText( null );
71
    alert.setContentText( message.getContent() );
72
    alert.initOwner( getWindow() );
73
74
    return alert;
75
  }
76
77
  @Override
78
  public Alert createAlertConfirmation( final AlertMessage message ) {
79
    final Alert alert = createAlertDialog( CONFIRMATION, message );
80
81
    alert.getButtonTypes().setAll( YES, NO, CANCEL );
82
83
    return alert;
84
  }
85
86
  @Override
87
  public Alert createAlertError( final AlertMessage message ) {
88
    return createAlertDialog( ERROR, message );
89
  }
90
91
  private Window getWindow() {
92
    return this.window;
93
  }
94
95
  @Override
96
  public void setWindow( Window window ) {
97
    this.window = window;
98
  }
99
}
1100
A src/main/java/com/scrivenvar/service/events/impl/FileType.java
1
/*
2
 * To change this license header, choose License Headers in Project Properties.
3
 * To change this template file, choose Tools | Templates
4
 * and open the template in the editor.
5
 */
6
package com.scrivenvar.service.events.impl;
7
8
/**
9
 * Lists known file types for creating document processors via the factory.
10
 *
11
 * @author White Magic Software, Ltd.
12
 */
13
public enum FileType {
14
  MARKDOWN("md", "markdown", "mkdown", "mdown", "mkdn", "mkd", "mdwn", "mdtxt", "mdtext", "text", "txt"),
15
  R_MARKDOWN("Rmd"),
16
  XML("xml");
17
18
  private final String[] extensions;
19
20
  private FileType(final String... extensions) {
21
    this.extensions = extensions;
22
  }
23
24
  /**
25
   * Returns true if the given file type aligns with the extension for this
26
   * enumeration.
27
   *
28
   * @param filetype The file extension to compare against the internal list.
29
   * @return true The given filetype equals (case insensitive) the internal
30
   * type.
31
   */
32
  public boolean isType(final String filetype) {
33
    boolean result = false;
34
35
    for (final String extension : this.extensions) {
36
      if (extension.equalsIgnoreCase(filetype)) {
37
        result = true;
38
        break;
39
      }
40
    }
41
42
    return result;
43
  }
44
}
145
A src/main/java/com/scrivenvar/service/impl/DefaultOptions.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
package com.scrivenvar.service.impl;
28
29
import static com.scrivenvar.Constants.PREFS_ROOT;
30
import static com.scrivenvar.Constants.PREFS_ROOT_OPTIONS;
31
import static com.scrivenvar.Constants.PREFS_ROOT_STATE;
32
import com.scrivenvar.service.Options;
33
import java.util.prefs.Preferences;
34
import static java.util.prefs.Preferences.userRoot;
35
36
/**
37
 * Persistent options user can change at runtime.
38
 *
39
 * @author Karl Tauber and White Magic Software, Ltd.
40
 */
41
public class DefaultOptions implements Options {
42
  private Preferences preferences;
43
  
44
  public DefaultOptions() {
45
    setPreferences( getRootPreferences().node( PREFS_ROOT_OPTIONS ) );
46
  }
47
48
  @Override
49
  public void put( final String key, final String value ) {
50
    getPreferences().put( key, value );
51
  }
52
  
53
  @Override
54
  public String get( final String key, final String defalutValue ) {
55
    return getPreferences().get( key, defalutValue );
56
  }
57
  
58
  private void setPreferences( final Preferences preferences ) {
59
    this.preferences = preferences;
60
  }
61
62
  private Preferences getRootPreferences() {
63
    return userRoot().node( PREFS_ROOT );
64
  }
65
66
  @Override
67
  public Preferences getState() {
68
    return getRootPreferences().node( PREFS_ROOT_STATE );
69
  }
70
71
  private Preferences getPreferences() {
72
    return this.preferences;
73
  }
74
}
175
A src/main/java/com/scrivenvar/service/impl/DefaultSettings.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.service.impl;
29
30
import static com.scrivenvar.Constants.SETTINGS_NAME;
31
import com.scrivenvar.service.Settings;
32
import java.io.IOException;
33
import java.net.URISyntaxException;
34
import java.net.URL;
35
import java.util.ArrayList;
36
import java.util.Iterator;
37
import java.util.List;
38
import java.util.Objects;
39
import java.util.stream.Collectors;
40
import org.apache.commons.configuration.ConfigurationException;
41
import org.apache.commons.configuration.PropertiesConfiguration;
42
43
/**
44
 * Responsible for loading settings that help avoid hard-coded assumptions.
45
 *
46
 * @author White Magic Software, Ltd.
47
 */
48
public class DefaultSettings implements Settings {
49
50
  private PropertiesConfiguration properties;
51
52
  public DefaultSettings()
53
    throws ConfigurationException, URISyntaxException, IOException {
54
    setProperties( createProperties() );
55
  }
56
57
  /**
58
   * Returns the value of a string property.
59
   *
60
   * @param property The property key.
61
   * @param defaultValue The value to return if no property key has been set.
62
   *
63
   * @return The property key value, or defaultValue when no key found.
64
   */
65
  @Override
66
  public String getSetting( final String property, final String defaultValue ) {
67
    return getSettings().getString( property, defaultValue );
68
  }
69
70
  /**
71
   * Returns the value of a string property.
72
   *
73
   * @param property The property key.
74
   * @param defaultValue The value to return if no property key has been set.
75
   *
76
   * @return The property key value, or defaultValue when no key found.
77
   */
78
  @Override
79
  public int getSetting( final String property, final int defaultValue ) {
80
    return getSettings().getInt( property, defaultValue );
81
  }
82
83
  /**
84
   * Returns a list of objects for a given setting.
85
   *
86
   * @param property The setting key name.
87
   * @param defaults The default values to return, which may be null.
88
   *
89
   * @return A list, possibly empty, never null.
90
   */
91
  @Override
92
  public List<Object> getSettingList( final String property, List<String> defaults ) {
93
    if( defaults == null ) {
94
      defaults = new ArrayList<>();
95
    }
96
97
    return getSettings().getList( property, defaults );
98
  }
99
100
  /**
101
   * Convert the generic list of property objects into strings.
102
   *
103
   * @param property The property value to coerce.
104
   * @param defaults The defaults values to use should the property be unset.
105
   *
106
   * @return The list of properties coerced from objects to strings.
107
   */
108
  @Override
109
  public List<String> getStringSettingList(
110
    final String property, final List<String> defaults ) {
111
    final List<Object> settings = getSettingList( property, defaults );
112
113
    return settings.stream()
114
      .map( object -> Objects.toString( object, null ) )
115
      .collect( Collectors.toList() );
116
  }
117
118
  /**
119
   * Convert a list of property objects into strings, with no default value.
120
   *
121
   * @param property The property value to coerce.
122
   *
123
   * @return The list of properties coerced from objects to strings.
124
   */
125
  @Override
126
  public List<String> getStringSettingList( final String property ) {
127
    return getStringSettingList( property, null );
128
  }
129
130
  /**
131
   * Returns a list of property names that begin with the given prefix.
132
   *
133
   * @param prefix The prefix to compare against each property name.
134
   *
135
   * @return The list of property names that have the given prefix.
136
   */
137
  @Override
138
  public Iterator<String> getKeys( final String prefix ) {
139
    return getSettings().getKeys( prefix );
140
  }
141
142
  private PropertiesConfiguration createProperties()
143
    throws ConfigurationException {
144
    final URL url = getPropertySource();
145
146
    return url == null
147
      ? new PropertiesConfiguration()
148
      : new PropertiesConfiguration( url );
149
  }
150
151
  private URL getPropertySource() {
152
    return getClass().getResource( getSettingsFilename() );
153
  }
154
155
  private String getSettingsFilename() {
156
    return SETTINGS_NAME;
157
  }
158
159
  private void setProperties( final PropertiesConfiguration configuration ) {
160
    this.properties = configuration;
161
  }
162
163
  private PropertiesConfiguration getSettings() {
164
    return this.properties;
165
  }
166
}
1167
A src/main/java/com/scrivenvar/test/TestDefinitionPane.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.test;
29
30
import com.scrivenvar.definition.DefinitionPane;
31
import static javafx.application.Application.launch;
32
import javafx.scene.control.TreeItem;
33
import javafx.scene.control.TreeView;
34
import javafx.stage.Stage;
35
36
/**
37
 * TestDefinitionPane application for debugging.
38
 */
39
public final class TestDefinitionPane extends TestHarness {
40
  /**
41
   * Application entry point.
42
   *
43
   * @param stage The primary application stage.
44
   *
45
   * @throws Exception Could not read configuration file.
46
   */
47
  @Override
48
  public void start( final Stage stage ) throws Exception {
49
    super.start( stage );
50
51
    TreeView<String> root = createTreeView();
52
    DefinitionPane pane = createDefinitionPane( root );
53
54
    test( pane, "language.ai.", "article" );
55
    test( pane, "language.ai", "ai" );
56
    test( pane, "l", "location" );
57
    test( pane, "la", "language" );
58
    test( pane, "c.p.n", "name" );
59
    test( pane, "c.p.n.", "First" );
60
    test( pane, "...", "c" );
61
    test( pane, "foo", "c" );
62
    test( pane, "foo.bar", "c" );
63
    test( pane, "", "c" );
64
    test( pane, "c", "protagonist" );
65
    test( pane, "c.", "protagonist" );
66
    test( pane, "c.p", "protagonist" );
67
    test( pane, "c.protagonist", "protagonist" );
68
69
    System.exit( 0 );
70
  }
71
72
  private void test( DefinitionPane pane, String path, String value ) {
73
    System.out.println( "---------------------------" );
74
    System.out.println( "Find Path: '" + path + "'" );
75
    final TreeItem<String> node = pane.findNode( path );
76
    System.out.println( "Path Node: " + node );
77
    System.out.println( "Node Val : " + node.getValue() );
78
  }
79
80
  public static void main( String[] args ) {
81
    launch( args );
82
  }
83
}
184
A src/main/java/com/scrivenvar/test/TestHarness.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.test;
29
30
import static com.scrivenvar.Messages.get;
31
import com.scrivenvar.definition.DefinitionPane;
32
import com.scrivenvar.definition.yaml.YamlParser;
33
import com.scrivenvar.definition.yaml.YamlTreeAdapter;
34
import java.io.IOException;
35
import java.io.InputStream;
36
import javafx.application.Application;
37
import javafx.scene.Scene;
38
import javafx.scene.control.TreeView;
39
import javafx.scene.layout.BorderPane;
40
import javafx.stage.Stage;
41
import org.fxmisc.flowless.VirtualizedScrollPane;
42
import org.fxmisc.richtext.StyleClassedTextArea;
43
44
/**
45
 * TestDefinitionPane application for debugging and head-banging.
46
 */
47
public abstract class TestHarness extends Application {
48
49
  private static Application app;
50
  private Scene scene;
51
52
  /**
53
   * Application entry point.
54
   *
55
   * @param stage The primary application stage.
56
   *
57
   * @throws Exception Could not read configuration file.
58
   */
59
  @Override
60
  public void start( final Stage stage ) throws Exception {
61
    initApplication();
62
    initScene();
63
    initStage( stage );
64
  }
65
  
66
  protected TreeView<String> createTreeView() throws IOException {
67
    return new YamlTreeAdapter( new YamlParser() ).adapt(
68
      asStream( "/com/scrivenvar/variables.yaml" ),
69
      get( "Pane.defintion.node.root.title" )
70
    );
71
  }
72
  
73
  protected DefinitionPane createDefinitionPane( TreeView<String> root ) {
74
    return new DefinitionPane( root );
75
  }
76
77
  private void initApplication() {
78
    app = this;
79
  }
80
81
  private void initScene() {
82
    final StyleClassedTextArea editor = new StyleClassedTextArea( false );
83
    final VirtualizedScrollPane<StyleClassedTextArea> scrollPane = new VirtualizedScrollPane<>( editor );
84
85
    final BorderPane borderPane = new BorderPane();
86
    borderPane.setPrefSize( 1024, 800 );
87
    borderPane.setCenter( scrollPane );
88
89
    setScene( new Scene( borderPane ) );
90
  }
91
92
  private void initStage( Stage stage ) {
93
    stage.setScene( getScene() );
94
  }
95
96
  private Scene getScene() {
97
    return this.scene;
98
  }
99
100
  private void setScene( Scene scene ) {
101
    this.scene = scene;
102
  }
103
104
  private static Application getApplication() {
105
    return app;
106
  }
107
108
  public static void showDocument( String uri ) {
109
    getApplication().getHostServices().showDocument( uri );
110
  }
111
112
  protected InputStream asStream( String resource ) {
113
    return getClass().getResourceAsStream( resource );
114
  }
115
}
1116
A src/main/java/com/scrivenvar/test/TestVariableNameProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.test;
29
30
import com.scrivenvar.definition.VariableTreeItem;
31
import java.util.Collection;
32
import java.util.HashMap;
33
import java.util.Map;
34
import static java.util.concurrent.ThreadLocalRandom.current;
35
import java.util.concurrent.TimeUnit;
36
import static java.util.concurrent.TimeUnit.DAYS;
37
import static java.util.concurrent.TimeUnit.HOURS;
38
import static java.util.concurrent.TimeUnit.MILLISECONDS;
39
import static java.util.concurrent.TimeUnit.MINUTES;
40
import static java.util.concurrent.TimeUnit.NANOSECONDS;
41
import static java.util.concurrent.TimeUnit.SECONDS;
42
import static javafx.application.Application.launch;
43
import javafx.scene.control.TreeItem;
44
import javafx.scene.control.TreeView;
45
import javafx.stage.Stage;
46
import org.ahocorasick.trie.*;
47
import org.ahocorasick.trie.Trie.TrieBuilder;
48
import static org.apache.commons.lang.RandomStringUtils.randomNumeric;
49
import org.apache.commons.lang.StringUtils;
50
51
/**
52
 * Tests substituting variable definitions with their values in a swath of text.
53
 *
54
 * @author White Magic Software, Ltd.
55
 */
56
public class TestVariableNameProcessor extends TestHarness {
57
58
  private final static int TEXT_SIZE = 1000000;
59
  private final static int MATCHES_DIVISOR = 1000;
60
61
  private final static StringBuilder SOURCE
62
    = new StringBuilder( randomNumeric( TEXT_SIZE ) );
63
64
  private final static boolean DEBUG = false;
65
66
  public TestVariableNameProcessor() {
67
  }
68
69
  @Override
70
  public void start( final Stage stage ) throws Exception {
71
    super.start( stage );
72
73
    final TreeView<String> treeView = createTreeView();
74
    final Map<String, String> definitions = new HashMap<>();
75
76
    populate( treeView.getRoot(), definitions );
77
    injectVariables( definitions );
78
79
    final String text = SOURCE.toString();
80
81
    show( text );
82
83
    long duration = System.nanoTime();
84
85
    // TODO: Test replaceEach (with intercoluated variables) and replaceEachRepeatedly
86
    // (without intercoluation).
87
    final String result = testBorAhoCorasick( text, definitions );
88
89
    duration = System.nanoTime() - duration;
90
91
    show( result );
92
    System.out.println( elapsed( duration ) );
93
94
    System.exit( 0 );
95
  }
96
97
  private void show( final String s ) {
98
    if( DEBUG ) {
99
      System.out.printf( "%s\n\n", s );
100
    }
101
  }
102
103
  private String testBorAhoCorasick(
104
    final String text,
105
    final Map<String, String> definitions ) {
106
    // Create a buffer sufficiently large that re-allocations are minimized.
107
    final StringBuilder sb = new StringBuilder( text.length() << 1 );
108
109
    final TrieBuilder builder = Trie.builder();
110
    builder.onlyWholeWords();
111
    builder.removeOverlaps();
112
113
    final String[] keys = keys( definitions );
114
115
    for( final String key : keys ) {
116
      builder.addKeyword( key );
117
    }
118
119
    final Trie trie = builder.build();
120
    final Collection<Emit> emits = trie.parseText( text );
121
122
    int prevIndex = 0;
123
124
    for( final Emit emit : emits ) {
125
      final int matchIndex = emit.getStart();
126
127
      sb.append( text.substring( prevIndex, matchIndex ) );
128
      sb.append( definitions.get( emit.getKeyword() ) );
129
      prevIndex = emit.getEnd() + 1;
130
    }
131
132
    // Add the remainder of the string (contains no more matches).
133
    sb.append( text.substring( prevIndex ) );
134
135
    return sb.toString();
136
  }
137
138
  private String testStringUtils(
139
    final String text, final Map<String, String> definitions ) {
140
    final String[] keys = keys( definitions );
141
    final String[] values = values( definitions );
142
143
    return StringUtils.replaceEach( text, keys, values );
144
  }
145
146
  private String[] keys( final Map<String, String> definitions ) {
147
    final int size = definitions.size();
148
    return definitions.keySet().toArray( new String[ size ] );
149
  }
150
151
  private String[] values( final Map<String, String> definitions ) {
152
    final int size = definitions.size();
153
    return definitions.values().toArray( new String[ size ] );
154
  }
155
156
  /**
157
   * Decomposes a period of time into days, hours, minutes, seconds,
158
   * milliseconds, and nanoseconds.
159
   *
160
   * @param duration Time in nanoseconds.
161
   *
162
   * @return A non-null, comma-separated string (without newline).
163
   */
164
  public String elapsed( long duration ) {
165
    final TimeUnit scale = NANOSECONDS;
166
167
    long days = scale.toDays( duration );
168
    duration -= DAYS.toMillis( days );
169
    long hours = scale.toHours( duration );
170
    duration -= HOURS.toMillis( hours );
171
    long minutes = scale.toMinutes( duration );
172
    duration -= MINUTES.toMillis( minutes );
173
    long seconds = scale.toSeconds( duration );
174
    duration -= SECONDS.toMillis( seconds );
175
    long millis = scale.toMillis( duration );
176
    duration -= MILLISECONDS.toMillis( seconds );
177
    long nanos = scale.toNanos( duration );
178
179
    return String.format(
180
      "%d days, %d hours, %d minutes, %d seconds, %d millis, %d nanos",
181
      days, hours, minutes, seconds, millis, nanos
182
    );
183
  }
184
185
  private void injectVariables( final Map<String, String> definitions ) {
186
    for( int i = (SOURCE.length() / MATCHES_DIVISOR) + 1; i > 0; i-- ) {
187
      final int r = current().nextInt( 1, SOURCE.length() );
188
      SOURCE.insert( r, randomKey( definitions ) );
189
    }
190
  }
191
192
  private String randomKey( final Map<String, String> map ) {
193
    final Object[] keys = map.keySet().toArray();
194
    final int r = current().nextInt( keys.length );
195
    return keys[ r ].toString();
196
  }
197
198
  private void populate( final TreeItem<String> parent, final Map<String, String> map ) {
199
    for( final TreeItem<String> child : parent.getChildren() ) {
200
      if( child.isLeaf() ) {
201
        final String key = asDefinition( ((VariableTreeItem<String>)child).toPath() );
202
        final String value = child.getValue();
203
204
        map.put( key, value );
205
      } else {
206
        populate( child, map );
207
      }
208
    }
209
  }
210
211
  private String asDefinition( final String key ) {
212
    return "$" + key + "$";
213
  }
214
215
  public static void main( String[] args ) {
216
    launch( args );
217
  }
218
}
1219
A src/main/java/com/scrivenvar/util/Action.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
28
package com.scrivenvar.util;
29
30
import javafx.beans.value.ObservableBooleanValue;
31
import javafx.event.ActionEvent;
32
import javafx.event.EventHandler;
33
import javafx.scene.input.KeyCombination;
34
import de.jensd.fx.glyphs.GlyphIcons;
35
36
/**
37
 * Simple action class
38
 *
39
 * @author Karl Tauber
40
 */
41
public class Action
42
{
43
	public final String text;
44
	public final KeyCombination accelerator;
45
	public final GlyphIcons icon;
46
	public final EventHandler<ActionEvent> action;
47
	public final ObservableBooleanValue disable;
48
49
	public Action(String text, String accelerator, GlyphIcons icon,
50
		EventHandler<ActionEvent> action)
51
	{
52
		this(text, accelerator, icon, action, null);
53
	}
54
55
	public Action(String text, String accelerator, GlyphIcons icon,
56
		EventHandler<ActionEvent> action, ObservableBooleanValue disable)
57
	{
58
		this.text = text;
59
		this.accelerator = (accelerator != null) ? KeyCombination.valueOf(accelerator) : null;
60
		this.icon = icon;
61
		this.action = action;
62
		this.disable = disable;
63
	}
64
}
165
A src/main/java/com/scrivenvar/util/ActionUtils.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
package com.scrivenvar.util;
28
29
import de.jensd.fx.glyphs.fontawesome.utils.FontAwesomeIconFactory;
30
import javafx.scene.Node;
31
import javafx.scene.control.Button;
32
import javafx.scene.control.Menu;
33
import javafx.scene.control.MenuItem;
34
import javafx.scene.control.Separator;
35
import javafx.scene.control.SeparatorMenuItem;
36
import javafx.scene.control.ToolBar;
37
import javafx.scene.control.Tooltip;
38
39
/**
40
 * Action utilities
41
 *
42
 * @author Karl Tauber
43
 */
44
public class ActionUtils {
45
46
  public static Menu createMenu( final String text, final Action... actions ) {
47
    return new Menu( text, null, createMenuItems( actions ) );
48
  }
49
50
  public static MenuItem[] createMenuItems( Action... actions ) {
51
    MenuItem[] menuItems = new MenuItem[ actions.length ];
52
    for( int i = 0; i < actions.length; i++ ) {
53
      menuItems[ i ] = (actions[ i ] != null)
54
        ? createMenuItem( actions[ i ] )
55
        : new SeparatorMenuItem();
56
    }
57
    return menuItems;
58
  }
59
60
  public static MenuItem createMenuItem( Action action ) {
61
    MenuItem menuItem = new MenuItem( action.text );
62
    if( action.accelerator != null ) {
63
      menuItem.setAccelerator( action.accelerator );
64
    }
65
66
    if( action.icon != null ) {
67
      menuItem.setGraphic( FontAwesomeIconFactory.get().createIcon( action.icon ) );
68
    }
69
70
    menuItem.setOnAction( action.action );
71
72
    if( action.disable != null ) {
73
      menuItem.disableProperty().bind( action.disable );
74
    }
75
76
    menuItem.setMnemonicParsing( true );
77
78
    return menuItem;
79
  }
80
81
  public static ToolBar createToolBar( Action... actions ) {
82
    return new ToolBar( createToolBarButtons( actions ) );
83
  }
84
85
  public static Node[] createToolBarButtons( Action... actions ) {
86
    Node[] buttons = new Node[ actions.length ];
87
    for( int i = 0; i < actions.length; i++ ) {
88
      buttons[ i ] = (actions[ i ] != null)
89
        ? createToolBarButton( actions[ i ] )
90
        : new Separator();
91
    }
92
    return buttons;
93
  }
94
95
  public static Button createToolBarButton( Action action ) {
96
    Button button = new Button();
97
    button.setGraphic( FontAwesomeIconFactory.get().createIcon( action.icon, "1.2em" ) );
98
    String tooltip = action.text;
99
    if( tooltip.endsWith( "..." ) ) {
100
      tooltip = tooltip.substring( 0, tooltip.length() - 3 );
101
    }
102
    if( action.accelerator != null ) {
103
      tooltip += " (" + action.accelerator.getDisplayText() + ')';
104
    }
105
    button.setTooltip( new Tooltip( tooltip ) );
106
    button.setFocusTraversable( false );
107
    button.setOnAction( action.action );
108
    if( action.disable != null ) {
109
      button.disableProperty().bind( action.disable );
110
    }
111
    return button;
112
  }
113
}
1114
A src/main/java/com/scrivenvar/util/Item.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
package com.scrivenvar.util;
28
29
/**
30
 * Simple item for a ChoiceBox, ComboBox or ListView. Consists of a string name
31
 * and a value object. toString() returns the name. equals() compares the value
32
 * and hashCode() returns the hash code of the value.
33
 *
34
 * @author Karl Tauber
35
 * @param <V> The type of item value.
36
 */
37
public class Item<V> {
38
39
  public final String name;
40
  public final V value;
41
42
  public Item( final String name, final V value ) {
43
    this.name = name;
44
    this.value = value;
45
  }
46
47
  @Override
48
  public boolean equals( final Object obj ) {
49
    if( this == obj ) {
50
      return true;
51
    }
52
    if( !(obj instanceof Item) ) {
53
      return false;
54
    }
55
    return Utils.safeEquals( value, ((Item<?>)obj).value );
56
  }
57
58
  @Override
59
  public int hashCode() {
60
    return (value != null) ? value.hashCode() : 0;
61
  }
62
63
  @Override
64
  public String toString() {
65
    return name;
66
  }
67
}
168
A src/main/java/com/scrivenvar/util/Lists.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.util;
29
30
import java.util.List;
31
32
/**
33
 * Convenience class that provides a clearer API for obtaining list elements.
34
 *
35
 * @author White Magic Software, Ltd.
36
 */
37
public final class Lists {
38
39
  private Lists() {
40
  }
41
42
  /**
43
   * Returns the first item in the given list, or null if not found.
44
   *
45
   * @param <T> The generic list type.
46
   * @param list The list that may have a first item.
47
   *
48
   * @return null if the list is null or there is no first item.
49
   */
50
  public static <T> T getFirst( final List<T> list ) {
51
    return getFirst( list, null );
52
  }
53
54
  /**
55
   * Returns the last item in the given list, or null if not found.
56
   *
57
   * @param <T> The generic list type.
58
   * @param list The list that may have a last item.
59
   *
60
   * @return null if the list is null or there is no last item.
61
   */
62
  public static <T> T getLast( final List<T> list ) {
63
    return getLast( list, null );
64
  }
65
66
  /**
67
   * Returns the first item in the given list, or t if not found.
68
   *
69
   * @param <T> The generic list type.
70
   * @param list The list that may have a first item.
71
   * @param t The default return value.
72
   *
73
   * @return null if the list is null or there is no first item.
74
   */
75
  public static <T> T getFirst( final List<T> list, final T t ) {
76
    return isEmpty( list ) ? t : list.get( 0 );
77
  }
78
79
  /**
80
   * Returns the last item in the given list, or t if not found.
81
   *
82
   * @param <T> The generic list type.
83
   * @param list The list that may have a last item.
84
   * @param t The default return value.
85
   *
86
   * @return null if the list is null or there is no last item.
87
   */
88
  public static <T> T getLast( final List<T> list, final T t ) {
89
    return isEmpty( list ) ? t : list.get( list.size() - 1 );
90
  }
91
92
  /**
93
   * Returns true if the given list is null or empty.
94
   *
95
   * @param <T> The generic list type.
96
   * @param list The list that has a last item.
97
   *
98
   * @return true The list is empty.
99
   */
100
  public static <T> boolean isEmpty( final List<T> list ) {
101
    return list == null || list.isEmpty();
102
  }
103
}
1104
A src/main/java/com/scrivenvar/util/StageState.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
package com.scrivenvar.util;
28
29
import java.util.prefs.Preferences;
30
import javafx.application.Platform;
31
import javafx.scene.shape.Rectangle;
32
import javafx.stage.Stage;
33
import javafx.stage.WindowEvent;
34
35
/**
36
 * Saves and restores Stage state (window bounds, maximized, fullScreen).
37
 *
38
 * @author Karl Tauber
39
 */
40
public class StageState {
41
42
  public static final String K_PANE_SPLIT_DEFINITION = "pane.split.definition";
43
  public static final String K_PANE_SPLIT_EDITOR = "pane.split.editor";
44
  public static final String K_PANE_SPLIT_PREVIEW = "pane.split.preview";
45
46
  private final Stage stage;
47
  private final Preferences state;
48
49
  private Rectangle normalBounds;
50
  private boolean runLaterPending;
51
52
  public StageState( final Stage stage, final Preferences state ) {
53
    this.stage = stage;
54
    this.state = state;
55
56
    restore();
57
58
    stage.addEventHandler( WindowEvent.WINDOW_HIDING, e -> save() );
59
60
    stage.xProperty().addListener( (ob, o, n) -> boundsChanged() );
61
    stage.yProperty().addListener( (ob, o, n) -> boundsChanged() );
62
    stage.widthProperty().addListener( (ob, o, n) -> boundsChanged() );
63
    stage.heightProperty().addListener( (ob, o, n) -> boundsChanged() );
64
  }
65
66
  private void save() {
67
    final Rectangle bounds = isNormalState() ? getStageBounds() : normalBounds;
68
    
69
    if( bounds != null ) {
70
      state.putDouble( "windowX", bounds.getX() );
71
      state.putDouble( "windowY", bounds.getY() );
72
      state.putDouble( "windowWidth", bounds.getWidth() );
73
      state.putDouble( "windowHeight", bounds.getHeight() );
74
    }
75
    
76
    state.putBoolean( "windowMaximized", stage.isMaximized() );
77
    state.putBoolean( "windowFullScreen", stage.isFullScreen() );
78
  }
79
80
  private void restore() {
81
    final double x = state.getDouble( "windowX", Double.NaN );
82
    final double y = state.getDouble( "windowY", Double.NaN );
83
    final double w = state.getDouble( "windowWidth", Double.NaN );
84
    final double h = state.getDouble( "windowHeight", Double.NaN );
85
    final boolean maximized = state.getBoolean( "windowMaximized", false );
86
    final boolean fullScreen = state.getBoolean( "windowFullScreen", false );
87
88
    if( !Double.isNaN( x ) && !Double.isNaN( y ) ) {
89
      stage.setX( x );
90
      stage.setY( y );
91
    } // else: default behavior is center on screen
92
93
    if( !Double.isNaN( w ) && !Double.isNaN( h ) ) {
94
      stage.setWidth( w );
95
      stage.setHeight( h );
96
    } // else: default behavior is use scene size
97
98
    if( fullScreen != stage.isFullScreen() ) {
99
      stage.setFullScreen( fullScreen );
100
    }
101
    
102
    if( maximized != stage.isMaximized() ) {
103
      stage.setMaximized( maximized );
104
    }
105
  }
106
107
  /**
108
   * Remembers the window bounds when the window is not iconified, maximized or
109
   * in fullScreen.
110
   */
111
  private void boundsChanged() {
112
    // avoid too many (and useless) runLater() invocations
113
    if( runLaterPending ) {
114
      return;
115
    }
116
    
117
    runLaterPending = true;
118
119
    // must use runLater() to ensure that change of all properties
120
    // (x, y, width, height, iconified, maximized and fullScreen)
121
    // has finished
122
    Platform.runLater( () -> {
123
      runLaterPending = false;
124
125
      if( isNormalState() ) {
126
        normalBounds = getStageBounds();
127
      }
128
    } );
129
  }
130
131
  private boolean isNormalState() {
132
    return !stage.isIconified() && !stage.isMaximized() && !stage.isFullScreen();
133
  }
134
135
  private Rectangle getStageBounds() {
136
    return new Rectangle( stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight() );
137
  }
138
}
1139
A src/main/java/com/scrivenvar/util/Utils.java
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
package com.scrivenvar.util;
28
29
import java.util.ArrayList;
30
import java.util.prefs.Preferences;
31
32
/**
33
 * @author Karl Tauber
34
 */
35
public class Utils {
36
37
  public static boolean safeEquals( final Object o1, final Object o2 ) {
38
    if( o1 == o2 ) {
39
      return true;
40
    }
41
    if( o1 == null || o2 == null ) {
42
      return false;
43
    }
44
    return o1.equals( o2 );
45
  }
46
47
  public static boolean isNullOrEmpty( final String s ) {
48
    return s == null || s.isEmpty();
49
  }
50
51
  public static String ltrim( final String s ) {
52
    int i = 0;
53
54
    while( i < s.length() && Character.isWhitespace( s.charAt( i ) ) ) {
55
      i++;
56
    }
57
58
    return s.substring( i );
59
  }
60
61
  public static String rtrim( final String s ) {
62
    int i = s.length() - 1;
63
64
    while( i >= 0 && Character.isWhitespace( s.charAt( i ) ) ) {
65
      i--;
66
    }
67
68
    return s.substring( 0, i + 1 );
69
  }
70
71
  public static void putPrefs( Preferences prefs, String key, String value, String def ) {
72
    if( value != def && !value.equals( def ) ) {
73
      prefs.put( key, value );
74
    } else {
75
      prefs.remove( key );
76
    }
77
  }
78
79
  public static void putPrefsInt( Preferences prefs, String key, int value, int def ) {
80
    if( value != def ) {
81
      prefs.putInt( key, value );
82
    } else {
83
      prefs.remove( key );
84
    }
85
  }
86
87
  public static void putPrefsBoolean( Preferences prefs, String key, boolean value, boolean def ) {
88
    if( value != def ) {
89
      prefs.putBoolean( key, value );
90
    } else {
91
      prefs.remove( key );
92
    }
93
  }
94
95
  public static String[] getPrefsStrings( final Preferences prefs, String key ) {
96
    final ArrayList<String> arr = new ArrayList<>( 256 );
97
98
    for( int i = 0; i < 10000; i++ ) {
99
      final String s = prefs.get( key + (i + 1), null );
100
101
      if( s == null ) {
102
        break;
103
      }
104
105
      arr.add( s );
106
    }
107
108
    return arr.toArray( new String[ arr.size() ] );
109
  }
110
111
  public static void putPrefsStrings( Preferences prefs, String key, String[] strings ) {
112
    for( int i = 0; i < strings.length; i++ ) {
113
      prefs.put( key + (i + 1), strings[ i ] );
114
    }
115
116
    for( int i = strings.length; prefs.get( key + (i + 1), null ) != null; i++ ) {
117
      prefs.remove( key + (i + 1) );
118
    }
119
  }
120
}
1121
A src/main/resources/META-INF/services/com.scrivenvar.service.Options
1
1
com.scrivenvar.service.impl.DefaultOptions
A src/main/resources/META-INF/services/com.scrivenvar.service.Settings
1
1
com.scrivenvar.service.impl.DefaultSettings
A src/main/resources/META-INF/services/com.scrivenvar.service.events.AlertService
1
1
com.scrivenvar.service.events.impl.DefaultAlertService
A src/main/resources/com/scrivenvar/build.sh
1
#!/bin/bash
2
3
INKSCAPE=/usr/bin/inkscape
4
5
declare -a SIZES=("16" "32" "64" "128" "256" "512")
6
7
for i in "${SIZES[@]}"; do
8
  # -y: export background opacity 0
9
  $INKSCAPE -y 0 -z -f "logo.svg" -w "${i}" -e "logo${i}.png"
10
done
11
112
A src/main/resources/com/scrivenvar/editor/Markdown.css
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
28
.markdown-editor {
29
  -fx-font-size: 14px;
30
}
31
32
/*---- headers ----*/
33
34
.markdown-editor .h1 { -fx-font-size: 2.25em; }
35
.markdown-editor .h2 { -fx-font-size: 1.75em; }
36
.markdown-editor .h3 { -fx-font-size: 1.5em; }
37
.markdown-editor .h4 { -fx-font-size: 1.25em; }
38
.markdown-editor .h5 { -fx-font-size: 1.1em; }
39
.markdown-editor .h6 { -fx-font-size: 1em; }
40
41
.markdown-editor .h1,
42
.markdown-editor .h2,
43
.markdown-editor .h3,
44
.markdown-editor .h4,
45
.markdown-editor .h5,
46
.markdown-editor .h6 {
47
  -fx-font-weight: bold;
48
  -fx-fill: derive(crimson, -20%);
49
}
50
51
52
/*---- inlines ----*/
53
54
.markdown-editor .strong {
55
  -fx-font-weight: bold;
56
}
57
58
.markdown-editor .em {
59
  -fx-font-style: italic;
60
}
61
62
.markdown-editor .del {
63
  -fx-strikethrough: true;
64
}
65
66
.markdown-editor .a {
67
  -fx-fill: #4183C4 !important;
68
}
69
70
.markdown-editor .img {
71
  -fx-fill: #4183C4 !important;
72
}
73
74
.markdown-editor .code {
75
  -fx-font-family: monospace;
76
  -fx-fill: #090 !important;
77
}
78
79
80
/*---- blocks ----*/
81
82
.markdown-editor .pre {
83
  -fx-font-family: monospace;
84
  -fx-fill: #060 !important;
85
}
86
87
.markdown-editor .blockquote {
88
  -fx-fill: #777;
89
}
90
91
92
/*---- lists ----*/
93
94
.markdown-editor .ul {
95
}
96
97
.markdown-editor .ol {
98
}
99
100
.markdown-editor .li {
101
  -fx-fill: #444;
102
}
103
104
.markdown-editor .dl {
105
}
106
107
.markdown-editor .dt {
108
  -fx-font-weight: bold;
109
  -fx-font-style: italic;
110
}
111
112
.markdown-editor .dd {
113
  -fx-fill: #444;
114
}
115
116
117
/*---- table ----*/
118
119
.markdown-editor .table {
120
  -fx-font-family: monospace;
121
}
122
123
.markdown-editor .thead {
124
}
125
126
.markdown-editor .tbody {
127
}
128
129
.markdown-editor .caption {
130
}
131
132
.markdown-editor .th {
133
  -fx-font-weight: bold;
134
}
135
136
.markdown-editor .tr {
137
}
138
139
.markdown-editor .td {
140
}
141
142
143
/*---- misc ----*/
144
145
.markdown-editor .html {
146
  -fx-font-family: monospace;
147
  -fx-fill: derive(crimson, -50%);
148
}
149
.markdown-editor .monospace {
150
  -fx-font-family: monospace;
151
}
1152
A src/main/resources/com/scrivenvar/logo.svg
1
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
<!-- Created with Inkscape (http://www.inkscape.org/) -->
3
4
<svg
5
   xmlns:dc="http://purl.org/dc/elements/1.1/"
6
   xmlns:cc="http://creativecommons.org/ns#"
7
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8
   xmlns:svg="http://www.w3.org/2000/svg"
9
   xmlns="http://www.w3.org/2000/svg"
10
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12
   id="svg2"
13
   version="1.1"
14
   inkscape:version="0.91 r13725"
15
   width="512"
16
   height="512"
17
   viewBox="0 0 512 512"
18
   sodipodi:docname="logo.svg">
19
  <metadata
20
     id="metadata8">
21
    <rdf:RDF>
22
      <cc:Work
23
         rdf:about="">
24
        <dc:format>image/svg+xml</dc:format>
25
        <dc:type
26
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
27
        <dc:title></dc:title>
28
      </cc:Work>
29
    </rdf:RDF>
30
  </metadata>
31
  <defs
32
     id="defs6" />
33
  <sodipodi:namedview
34
     pagecolor="#ffffff"
35
     bordercolor="#666666"
36
     borderopacity="1"
37
     objecttolerance="10"
38
     gridtolerance="10"
39
     guidetolerance="10"
40
     inkscape:pageopacity="0"
41
     inkscape:pageshadow="2"
42
     inkscape:window-width="640"
43
     inkscape:window-height="480"
44
     id="namedview4"
45
     showgrid="false"
46
     fit-margin-top="0"
47
     fit-margin-left="0"
48
     fit-margin-right="0"
49
     fit-margin-bottom="0"
50
     inkscape:zoom="1.2682274"
51
     inkscape:cx="15.646213"
52
     inkscape:cy="213.34955"
53
     inkscape:current-layer="svg2" />
54
  <path
55
     style="fill:#ce6200;fill-opacity:1"
56
     d="m 203.2244,511.85078 c -60.01827,-1.2968 -121.688643,-6.5314 -192.436493,-16.334 -5.8078027,-0.8047 -10.66110747,-1.561 -10.78511762,-1.6806 -0.12404567,-0.1196 3.90488112,-4.5812 8.95313512,-9.9147 32.9484785,-34.8102 70.4314485,-73.8923 104.1521555,-108.5956 l 11.87611,-12.2221 5.48905,-10.2177 c 35.82801,-66.6927 75.13064,-128.5665 105.90637,-166.7277 6.13805,-7.611 10.21451,-12.0689 17.28719,-18.9048 36.6818,-35.4537 108.27279,-83.724003 206.0323,-138.917303 22.10365,-12.47935 51.93386,-28.64995037 52.26391,-28.33165037 0.38883,0.37499 -2.35932,25.95575037 -4.86585,45.29275037 -7.28943,56.236403 -17.04619,103.128903 -28.07642,134.939803 -7.19617,20.7536 -14.81287,35.152 -22.9667,43.4155 -3.60444,3.6529 -6.58328,5.7941 -10.1313,7.2825 l -2.56414,1.0756 -53.43164,0.1713 -53.43166,0.1713 3.69973,1.8547 c 26.78565,13.4282 52.58051,27.5241 59.57122,32.5533 4.48397,3.2259 4.41278,2.9854 1.59124,5.3784 -26.99514,22.8955 -74.52961,44.0013 -140.23089,62.2641 -26.34995,7.3244 -57.85469,14.6842 -86.99871,20.3237 l -10.26943,1.9871 -52.01052,53.2733 -52.010524,53.2732 -29.459801,15.1165 c -26.4100885,13.5517 -29.3446639,15.1388 -28.347645,15.3311 0.6117029,0.118 4.0894221,0.2188 7.7282726,0.2239 3.6388854,0.01 16.1273694,0.2329 27.7522124,0.5059 51.576376,1.2116 146.083985,1.512 170.154295,0.5409 34.66996,-1.3988 52.7606,-2.9325 67.58258,-5.7293 2.68664,-0.507 4.82907,-0.9755 4.76094,-1.0412 -0.0681,-0.066 -3.24733,-0.8833 -7.0649,-1.8169 -8.04133,-1.9664 -25.10167,-5.3107 -41.1231,-8.0612 -47.6405,-8.1787 -65.48708,-12.0107 -74.13028,-15.9169 -3.90548,-1.7651 -7.13816,-4.7659 -8.12937,-7.5463 -1.01822,-2.8562 -0.92214,-6.5271 0.23315,-8.9083 1.86563,-3.8451 6.14837,-6.7199 12.26745,-8.2345 16.96993,-4.2004 57.27977,-6.1832 90.36228,-4.4448 54.7332,2.8761 117.0767,13.1228 178.50212,29.3385 18.03514,4.7611 51.66065,14.656 51.22677,15.0744 -0.0824,0.08 -5.72762,-0.854 -12.54488,-2.0745 -40.1043,-7.18 -60.50854,-10.2888 -101.40822,-15.4507 -24.4851,-3.0902 -55.12614,-5.9915 -77.58876,-7.3465 -26.58826,-1.6039 -61.15821,-1.7754 -80.99202,-0.4019 l -3.19705,0.2214 8.70308,1.4934 c 51.89698,8.9047 77.51746,14.9877 88.00479,20.8948 6.9134,3.894 10.30497,9.4381 9.33333,15.2569 -1.50397,9.0066 -10.51381,14.0257 -32.00273,17.8278 -16.31374,2.8863 -47.27575,4.3845 -77.23553,3.7371 z"
57
     id="path4138" />
58
  <path
59
     style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
60
     d="m 214.76931,324.51908 c 60.83777,-14.1145 111.89562,-31.6251 144.40025,-49.5229 3.12602,-1.7213 5.81747,-3.2537 5.98106,-3.4054 0.40534,-0.3759 -13.76388,-7.9415 -34.63489,-18.4929 -7.52161,-3.8026 -9.82337,-5.3787 -12.0735,-8.2668 -5.14485,-6.6036 -5.96081,-14.8404 -2.20331,-22.2417 1.80288,-3.5512 5.69484,-7.3007 9.36158,-9.019 5.20851,-2.4407 1.18148,-2.2865 59.71223,-2.2865 l 52.81361,0 2.13233,-2.1984 c 2.78673,-2.8731 5.23414,-6.4981 8.23035,-12.1905 14.14966,-26.8827 26.71842,-78.3816 36.24347,-148.503303 0.76704,-5.6468 1.36194,-10.2983 1.32201,-10.3369 -0.0399,-0.038 -5.47754,2.9629 -12.08361,6.6697 l -12.01104,6.7396 -133.83068,137.037303 c -73.60688,75.3705 -134.81732,138.0567 -136.0232,139.3026 l -2.19251,2.2653 8.254,-1.8067 c 4.53969,-0.9937 12.01053,-2.6783 16.60185,-3.7435 z"
61
     id="path4136" />
62
  <path
63
     style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
64
     d="m 202.72524,284.43588 c 69.93294,-70.1332 135.4799,-131.9279 213.46406,-201.244203 7.71421,-6.8568 14.50542,-12.9341 15.09155,-13.5052 0.9482,-0.9239 0.96778,-0.9811 0.17761,-0.5188 -77.96496,45.611803 -139.23519,88.710503 -166.72539,117.278203 -18.81811,19.5556 -50.35654,64.861 -80.96704,116.3104 -0.91787,1.5427 1.02249,-0.3323 18.95921,-18.3204 z"
65
     id="path4142" />
66
  <path
67
     style="fill:#000000"
68
     d=""
69
     id="path4140"
70
     inkscape:connector-curvature="0" />
71
</svg>
172
A src/main/resources/com/scrivenvar/logo128.png
Binary file
A src/main/resources/com/scrivenvar/logo16.png
Binary file
A src/main/resources/com/scrivenvar/logo256.png
Binary file
A src/main/resources/com/scrivenvar/logo32.png
Binary file
A src/main/resources/com/scrivenvar/logo512.png
Binary file
A src/main/resources/com/scrivenvar/logo64.png
Binary file
A src/main/resources/com/scrivenvar/messages.properties
1
#
2
# Copyright 2016 Karl Tauber and White Magic Software, Ltd.
3
#
4
# All rights reserved.
5
#
6
# Redistribution and use in source and binary forms, with or without
7
# modification, are permitted provided that the following conditions are met:
8
#
9
#  * Redistributions of source code must retain the above copyright
10
#    notice, this list of conditions and the following disclaimer.
11
#
12
#  * Redistributions in binary form must reproduce the above copyright
13
#    notice, this list of conditions and the following disclaimer in the
14
#    documentation and/or other materials provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
#
28
29
# ########################################################################
30
#
31
# Main Application Window
32
#
33
# ########################################################################
34
35
# The application title should exist only once in the entire code base.
36
# All other references should either refer to this value via the Messages
37
# class, or indirectly using ${Main.title}.
38
Main.title=Scrivenvar
39
40
Main.menu.file=_File
41
Main.menu.file.new=New
42
Main.menu.file.open=Open...
43
Main.menu.file.close=Close
44
Main.menu.file.close_all=Close All
45
Main.menu.file.save=Save
46
Main.menu.file.save_all=Save All
47
Main.menu.file.exit=Exit
48
49
Main.menu.edit=_Edit
50
Main.menu.edit.undo=Undo
51
Main.menu.edit.redo=Redo
52
53
Main.menu.insert=_Insert
54
Main.menu.insert.bold=Bold
55
Main.menu.insert.italic=Italic
56
Main.menu.insert.strikethrough=Strikethrough
57
Main.menu.insert.blockquote=Blockquote
58
Main.menu.insert.code=Inline Code
59
Main.menu.insert.fenced_code_block=Fenced Code Block
60
Main.menu.insert.fenced_code_block.prompt=Enter code here
61
Main.menu.insert.link=Link...
62
Main.menu.insert.image=Image...
63
Main.menu.insert.header_1=Header 1
64
Main.menu.insert.header_1.prompt=header 1
65
Main.menu.insert.header_2=Header 2
66
Main.menu.insert.header_2.prompt=header 2
67
Main.menu.insert.header_3=Header 3
68
Main.menu.insert.header_3.prompt=header 3
69
Main.menu.insert.header_4=Header 4
70
Main.menu.insert.header_4.prompt=header 4
71
Main.menu.insert.header_5=Header 5
72
Main.menu.insert.header_5.prompt=header 5
73
Main.menu.insert.header_6=Header 6
74
Main.menu.insert.header_6.prompt=header 6
75
Main.menu.insert.unordered_list=Unordered List
76
Main.menu.insert.ordered_list=Ordered List
77
Main.menu.insert.horizontal_rule=Horizontal Rule
78
79
Main.menu.tools=_Tools
80
Main.menu.tools.options=Options
81
82
Main.menu.help=_Help
83
Main.menu.help.about=About ${Main.title}
84
85
# ########################################################################
86
#
87
# About Dialog
88
#
89
# ########################################################################
90
91
Dialog.about.title=About
92
Dialog.about.header=${Main.title}
93
Dialog.about.content=Copyright 2016 White Magic Software, Ltd.\n\nBased on Markdown Writer FX by Karl Tauber
94
95
# ########################################################################
96
#
97
# File Editor
98
#
99
# ########################################################################
100
101
FileEditor.untitled=Untitled
102
FileEditor.loadFailed.message=Failed to load ''{0}''.\n\nReason: {1}
103
FileEditor.loadFailed.title=Load
104
FileEditor.saveFailed.message=Failed to save ''{0}''.\n\nReason: {1}
105
FileEditor.saveFailed.title=Save
106
107
# ########################################################################
108
#
109
# File Open
110
#
111
# ########################################################################
112
113
Dialog.file.choose.open.title=Open File
114
Dialog.file.choose.save.title=Save File
115
116
Dialog.file.choose.filter.title.markdown=Markdown Files
117
Dialog.file.choose.filter.title.definition=Definition Files
118
Dialog.file.choose.filter.title.xml=XML Files
119
Dialog.file.choose.filter.title.all=All Files
120
121
# ########################################################################
122
#
123
# Alert Dialog
124
#
125
# ########################################################################
126
127
Alert.file.close.title=Close
128
Alert.file.close.text=Save changes to {0}?
129
130
# ########################################################################
131
#
132
# Definition Pane
133
#
134
# ########################################################################
135
136
Pane.defintion.node.root.title=Definitions
137
138
# Controls ###############################################################
139
140
# ########################################################################
141
#
142
# Browse Directory
143
#
144
# ########################################################################
145
146
BrowseDirectoryButton.chooser.title=Browse for local folder
147
BrowseDirectoryButton.tooltip=${BrowseDirectoryButton.chooser.title}
148
149
# ########################################################################
150
#
151
# Browse File
152
#
153
# ########################################################################
154
155
BrowseFileButton.chooser.title=Browse for local file
156
BrowseFileButton.chooser.allFilesFilter=All Files
157
BrowseFileButton.tooltip=${BrowseFileButton.chooser.title}
158
159
# Dialogs ################################################################
160
161
# ########################################################################
162
#
163
# Image
164
#
165
# ########################################################################
166
167
ImageDialog.title=Image
168
ImageDialog.chooser.imagesFilter=Images
169
ImageDialog.previewLabel.text=Markdown Preview\:
170
ImageDialog.textLabel.text=Alternate Text\:
171
ImageDialog.titleLabel.text=Title (tooltip)\:
172
ImageDialog.urlLabel.text=Image URL\:
173
174
# ########################################################################
175
#
176
# Hyperlink
177
#
178
# ########################################################################
179
180
LinkDialog.title=Link
181
LinkDialog.previewLabel.text=Markdown Preview\:
182
LinkDialog.textLabel.text=Link Text\:
183
LinkDialog.titleLabel.text=Title (tooltip)\:
184
LinkDialog.urlLabel.text=Link URL\:
185
186
# Options ################################################################
187
188
# ########################################################################
189
#
190
# Options Dialog
191
#
192
# ########################################################################
193
194
OptionsDialog.title=Options
195
OptionsDialog.generalTab.text=General
196
OptionsDialog.markdownTab.text=Markdown
197
198
# ########################################################################
199
#
200
# General Options Pane
201
#
202
# ########################################################################
203
204
GeneralOptionsPane.encodingLabel.text=En_coding\:
205
GeneralOptionsPane.lineSeparatorLabel.text=_Line separator\:
206
GeneralOptionsPane.lineSeparatorLabel2.text=(applies to new files only)
207
208
GeneralOptionsPane.platformDefault=Platform Default ({0})
209
GeneralOptionsPane.sepWindows=Windows (CRLF)
210
GeneralOptionsPane.sepUnix=Unix (LF)
211
212
# ########################################################################
213
#
214
# Markdown Options Pane
215
#
216
# ########################################################################
217
218
MarkdownOptionsPane.abbreviationsExtCheckBox.text=A_bbreviations in the way of
219
MarkdownOptionsPane.abbreviationsExtLink.text=Markdown Extra
220
MarkdownOptionsPane.anchorlinksExtCheckBox.text=_Anchor links in headers
221
MarkdownOptionsPane.atxHeaderSpaceExtCheckBox.text=Requires a space char after Atx \# header prefixes, so that \#dasdsdaf is not a header
222
MarkdownOptionsPane.autolinksExtCheckBox.text=_Plain (undelimited) autolinks in the way of
223
MarkdownOptionsPane.autolinksExtLink.text=Github-flavoured-Markdown
224
MarkdownOptionsPane.definitionListsExtCheckBox.text=_Definition lists in the way of
225
MarkdownOptionsPane.definitionListsExtLink.text=Markdown Extra
226
MarkdownOptionsPane.extAnchorLinksExtCheckBox.text=Generate anchor links for headers using complete contents of the header
227
MarkdownOptionsPane.fencedCodeBlocksExtCheckBox.text=_Fenced Code Blocks in the way of
228
MarkdownOptionsPane.fencedCodeBlocksExtLabel.text=or
229
MarkdownOptionsPane.fencedCodeBlocksExtLink.text=Markdown Extra
230
MarkdownOptionsPane.fencedCodeBlocksExtLink2.text=Github-flavoured-Markdown
231
MarkdownOptionsPane.forceListItemParaExtCheckBox.text=Force List and Definition Paragraph wrapping if it includes more than just a single paragraph
232
MarkdownOptionsPane.hardwrapsExtCheckBox.text=_Newlines in paragraph-like content as real line breaks, see
233
MarkdownOptionsPane.hardwrapsExtLink.text=Github-flavoured-Markdown
234
MarkdownOptionsPane.quotesExtCheckBox.text=Beautify single _quotes, double quotes and double angle quotes (\u00ab and \u00bb)
235
MarkdownOptionsPane.relaxedHrRulesExtCheckBox.text=Allow horizontal rules without a blank line following them
236
MarkdownOptionsPane.smartsExtCheckBox.text=Beautify apostrophes, _ellipses ("..." and ". . .") and dashes ("--" and "---")
237
MarkdownOptionsPane.strikethroughExtCheckBox.text=_Strikethrough
238
MarkdownOptionsPane.suppressHtmlBlocksExtCheckBox.text=Suppress the _output of HTML blocks
239
MarkdownOptionsPane.suppressInlineHtmlExtCheckBox.text=Suppress the o_utput of inline HTML elements
240
MarkdownOptionsPane.tablesExtCheckBox.text=_Tables similar to
241
MarkdownOptionsPane.tablesExtLabel.text=(like
242
MarkdownOptionsPane.tablesExtLabel2.text=tables, but with colspan support)
243
MarkdownOptionsPane.tablesExtLink.text=MultiMarkdown
244
MarkdownOptionsPane.tablesExtLink2.text=Markdown Extra
245
MarkdownOptionsPane.taskListItemsExtCheckBox.text=GitHub style task list items
246
MarkdownOptionsPane.wikilinksExtCheckBox.text=_Wiki-style links ("[[wiki link]]")
1247
A src/main/resources/com/scrivenvar/preview/webview.css
1
/*
2
This software is released under the MIT license:
3
4
Permission is hereby granted, free of charge, to any person obtaining a copy of
5
this software and associated documentation files (the "Software"), to deal in
6
the Software without restriction, including without limitation the rights to
7
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
the Software, and to permit persons to whom the Software is furnished to do so,
9
subject to the following conditions:
10
11
The above copyright notice and this permission notice shall be included in all
12
copies or substantial portions of the Software.
13
14
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
*/
21
22
/* Source: https://github.com/nicolashery/markdownpad-github */
23
24
/*  GitHub stylesheet for MarkdownPad (http://markdownpad.com) */
25
26
/* RESET
27
=============================================================================*/
28
29
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
30
  margin: 0;
31
  padding: 0;
32
  border: 0;
33
}
34
35
/* BODY
36
=============================================================================*/
37
38
body {
39
  font-family: Helvetica, arial, freesans, clean, sans-serif;
40
  font-size: 14px;
41
  line-height: 1.6;
42
  color: #333;
43
  background-color: #fff;
44
  padding: 20px;
45
  max-width: 960px;
46
  margin: 0 auto;
47
}
48
49
body>*:first-child {
50
  margin-top: 0 !important;
51
}
52
53
body>*:last-child {
54
  margin-bottom: 0 !important;
55
}
56
57
/* BLOCKS
58
=============================================================================*/
59
60
p, blockquote, ul, ol, dl, table, pre {
61
  margin: 15px 0;
62
}
63
64
/* HEADERS
65
=============================================================================*/
66
67
h1, h2, h3, h4, h5, h6 {
68
  margin: 20px 0 10px;
69
  padding: 0;
70
  font-weight: bold;
71
  -webkit-font-smoothing: antialiased;
72
}
73
74
h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code {
75
  font-size: inherit;
76
}
77
78
h1 {
79
  font-size: 28px;
80
  color: #000;
81
}
82
83
h2 {
84
  font-size: 24px;
85
  border-bottom: 1px solid #ccc;
86
  color: #000;
87
}
88
89
h3 {
90
  font-size: 18px;
91
}
92
93
h4 {
94
  font-size: 16px;
95
}
96
97
h5 {
98
  font-size: 14px;
99
}
100
101
h6 {
102
  color: #777;
103
  font-size: 14px;
104
}
105
106
body>h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h4:first-child, body>h5:first-child, body>h6:first-child {
107
  margin-top: 0;
108
  padding-top: 0;
109
}
110
111
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
112
  margin-top: 0;
113
  padding-top: 0;
114
}
115
116
h1+p, h2+p, h3+p, h4+p, h5+p, h6+p {
117
  margin-top: 10px;
118
}
119
120
/* LINKS
121
=============================================================================*/
122
123
a {
124
  color: #4183C4;
125
  text-decoration: none;
126
}
127
128
a:hover {
129
  text-decoration: underline;
130
}
131
132
/* LISTS
133
=============================================================================*/
134
135
ul, ol {
136
  padding-left: 30px;
137
}
138
139
ul li > :first-child, 
140
ol li > :first-child, 
141
ul li ul:first-of-type, 
142
ol li ol:first-of-type, 
143
ul li ol:first-of-type, 
144
ol li ul:first-of-type {
145
  margin-top: 0px;
146
}
147
148
ul ul, ul ol, ol ol, ol ul {
149
  margin-bottom: 0;
150
}
151
152
dl {
153
  padding: 0;
154
}
155
156
dl dt {
157
  font-size: 14px;
158
  font-weight: bold;
159
  font-style: italic;
160
  padding: 0;
161
  margin: 15px 0 5px;
162
}
163
164
dl dt:first-child {
165
  padding: 0;
166
}
167
168
dl dt>:first-child {
169
  margin-top: 0px;
170
}
171
172
dl dt>:last-child {
173
  margin-bottom: 0px;
174
}
175
176
dl dd {
177
  margin: 0 0 15px;
178
  padding: 0 15px;
179
}
180
181
dl dd>:first-child {
182
  margin-top: 0px;
183
}
184
185
dl dd>:last-child {
186
  margin-bottom: 0px;
187
}
188
189
/* CODE
190
=============================================================================*/
191
192
pre, code, tt {
193
  font-size: 12px;
194
  font-family: Consolas, "Liberation Mono", Courier, monospace;
195
}
196
197
code, tt {
198
  margin: 0 0px;
199
  padding: 0px 0px;
200
  white-space: nowrap;
201
  border: 1px solid #eaeaea;
202
  background-color: #f8f8f8;
203
  border-radius: 3px;
204
}
205
206
pre>code {
207
  margin: 0;
208
  padding: 0;
209
  white-space: pre;
210
  border: none;
211
  background: transparent;
212
}
213
214
pre {
215
  background-color: #f8f8f8;
216
  border: 1px solid #ccc;
217
  font-size: 13px;
218
  line-height: 19px;
219
  overflow: auto;
220
  padding: 6px 10px;
221
  border-radius: 3px;
222
}
223
224
pre code, pre tt {
225
  background-color: transparent;
226
  border: none;
227
}
228
229
kbd {
230
  -moz-border-bottom-colors: none;
231
  -moz-border-left-colors: none;
232
  -moz-border-right-colors: none;
233
  -moz-border-top-colors: none;
234
  background-color: #DDDDDD;
235
  background-image: linear-gradient(#F1F1F1, #DDDDDD);
236
  background-repeat: repeat-x;
237
  border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD;
238
  border-image: none;
239
  border-radius: 2px 2px 2px 2px;
240
  border-style: solid;
241
  border-width: 1px;
242
  font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
243
  line-height: 10px;
244
  padding: 1px 4px;
245
}
246
247
/* QUOTES
248
=============================================================================*/
249
250
blockquote {
251
  border-left: 4px solid #DDD;
252
  padding: 0 15px;
253
  color: #777;
254
}
255
256
blockquote>:first-child {
257
  margin-top: 0px;
258
}
259
260
blockquote>:last-child {
261
  margin-bottom: 0px;
262
}
263
264
/* HORIZONTAL RULES
265
=============================================================================*/
266
267
hr {
268
  clear: both;
269
  margin: 15px 0;
270
  height: 0px;
271
  overflow: hidden;
272
  border: none;
273
  background: transparent;
274
  border-bottom: 4px solid #ddd;
275
  padding: 0;
276
}
277
278
/* TABLES
279
=============================================================================*/
280
281
table th {
282
  font-weight: bold;
283
}
284
285
table th, table td {
286
  border: 1px solid #ccc;
287
  padding: 6px 13px;
288
}
289
290
table tr {
291
  border-top: 1px solid #ccc;
292
  background-color: #fff;
293
}
294
295
table tr:nth-child(2n) {
296
  background-color: #f8f8f8;
297
}
298
299
/* IMAGES
300
=============================================================================*/
301
302
img {
303
  max-width: 100%
304
}
305
306
/* CARET 
307
=============================================================================*/
308
309
#CARETPOSITION {
310
  border-right:1px solid #333;
311
  margin-right:-1px;
312
  animation: blink 1s linear infinite;
313
}
314
315
@keyframes blink {
316
  from {
317
    visibility:hidden;
318
  }
319
  50% {
320
    visibility:hidden;
321
  }
322
  to {
323
    visibility:visible;
324
  }
325
}
1326
A src/main/resources/com/scrivenvar/scene.css
1
/*
2
 * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com>
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 *  o Redistributions of source code must retain the above copyright
9
 *    notice, this list of conditions and the following disclaimer.
10
 *
11
 *  o Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
28
/*---- toolbar ----*/
29
30
.tool-bar {
31
	-fx-spacing: 0;
32
}
33
34
.tool-bar .button {
35
	-fx-background-color: transparent;
36
}
37
38
.tool-bar .button:hover {
39
	-fx-background-color: -fx-shadow-highlight-color, -fx-outer-border, -fx-inner-border, -fx-body-color;
40
	-fx-color: -fx-hover-base;
41
}
42
43
.tool-bar .button:armed {
44
	-fx-color: -fx-pressed-base;
45
}
146
A src/main/resources/com/scrivenvar/settings.properties
1
# ########################################################################
2
#
3
# Application
4
#
5
# ########################################################################
6
7
application.title=scrivenvar
8
application.package=com/${application.title}
9
application.messages= com.${application.title}.messages
10
11
# ########################################################################
12
#
13
# Preferences
14
#
15
# ########################################################################
16
17
preferences.root=com.${application.title}
18
preferences.root.state=state
19
preferences.root.options=options
20
21
# ########################################################################
22
#
23
# File References
24
#
25
# ########################################################################
26
27
file.stylesheet.scene=${application.package}/scene.css
28
file.stylesheet.markdown=${application.package}/editor/Markdown.css
29
file.stylesheet.preview=webview.css
30
31
file.logo.16 =${application.package}/logo16.png
32
file.logo.32 =${application.package}/logo32.png
33
file.logo.128=${application.package}/logo128.png
34
file.logo.256=${application.package}/logo256.png
35
file.logo.512=${application.package}/logo512.png
36
37
# ########################################################################
38
#
39
# Caret token
40
#
41
# ########################################################################
42
caret.token.base=CARETPOSITION
43
caret.token.markdown=%${constant.caret.token.base}%
44
caret.token.xml=<![CDATA[${constant.caret.token.markdown}]]>
45
caret.token.html=<span id="${caret.token.base}"></span>
46
47
# ########################################################################
48
#
49
# Filename Extensions
50
#
51
# ########################################################################
52
53
# Comma-separated list of definition filename extensions.
54
file.ext.definition.json=*.json
55
file.ext.definition.toml=*.toml
56
file.ext.definition.yaml=*.yml,*.yaml
57
file.ext.definition.properties=*.properties,*.props
58
59
# Comma-separated list of filename extensions.
60
filter.file.ext.markdown=*.Rmd,*.md,*.markdown,*.mkdown,*.mdown,*.mkdn,*.mkd,*.mdwn,*.mdtxt,*.mdtext,*.text,*.txt
61
filter.file.ext.definition=${file.ext.definition.yaml}
62
filter.file.ext.xml=*.xml,*.Rxml
63
filter.file.ext.all=*.*
64
65
# ########################################################################
66
#
67
# Variable Name Editor
68
#
69
# ########################################################################
70
71
# Maximum number of characters for a variable name. A variable is defined
72
# as one or more non-whitespace characters up to this maximum length.
73
editor.variable.maxLength=256
74
75
# ########################################################################
76
#
77
# Dialog Preferences
78
#
79
# ########################################################################
80
81
# docs.oracle.com/javase/8/javafx/api/javafx/scene/control/ButtonBar.html
82
dialog.alert.button.order.mac=L_HE+U+FBIX_NCYOA_R
83
dialog.alert.button.order.linux=L_HE+UNYACBXIO_R
84
dialog.alert.button.order.windows=L_E+U+FBXI_YNOCAH_R
85
86
# Ensures a consistent button order for alert dialogs across platforms (because
87
# the default button order on Linux defies all logic). Power to the people.
88
dialog.alert.button.order=${dialog.alert.button.order.windows}
189
A src/main/resources/com/scrivenvar/variables.yaml
1
---
2
c:
3
  protagonist:
4
    name:
5
      First: Chloe
6
      First_pos: $c.protagonist.name.First$'s
7
      Middle: Irene
8
      Family: Angelos
9
      nick:
10
        Father: Savant
11
        Mother: Sweetie
12
    colour:
13
      eyes: green
14
      hair: dark auburn
15
      syn_1: black
16
      syn_2: purple
17
      syn_11: teal
18
      syn_6: silver
19
      favourite: emerald green
20
    speech:
21
      tic: oh
22
    father:
23
      heritage: Greek
24
      name:
25
        Short: Bryce
26
        First: Bryson
27
        First_pos: $c.protagonist.father.name.First$'s
28
        Honourific: Mr.
29
      education: Masters
30
      vocation:
31
        name: robotics
32
        title: roboticist
33
      employer:
34
        name:
35
          Short: Rabota
36
          Full: $c.protagonist.father.employer.name.Short$ Designs
37
      hair:
38
        style: thick, curly
39
        colour: black
40
      eyes:
41
        colour: dark brown
42
      Endear: Dad
43
      vehicle: coupé
44
    mother:
45
      name:
46
        Short: Cass
47
        First: Cassandra
48
        First_pos: $c.protagonist.mother.name.First$'s
49
        Honourific: Mrs.
50
      education: PhD
51
      speech:
52
        tic: cute
53
        Honorific: Doctor
54
      vocation:
55
        article: an
56
        name: oceanography
57
        title: oceanographer
58
      employer:
59
        name:
60
          Full: Oregon State University
61
          Short: OSU
62
      eyes:
63
        colour: blue
64
      hair:
65
        style: thick, curly
66
        colour: dark brown
67
      Endear: Mom
68
      Endear_pos: Mom's
69
    uncle:
70
      name:
71
        First: Damian
72
        First_pos: $c.protagonist.uncle.name.First$'s
73
        Family: Moros
74
      hands:
75
        fingers:
76
          shape: long, bony
77
    friend:
78
      primary:
79
        name:
80
          First: Gerard
81
          First_pos: $c.protagonist.friend.primary.name.First$'s
82
          Family: Baran
83
          Family_pos: $c.protagonist.friend.primary.name.Family$'s
84
        favourite:
85
          colour: midnight blue
86
        eyes:
87
          colour: hazel
88
        mother:
89
          name:
90
            First: Isabella
91
            Short: Izzy
92
            Honourific: Mrs.
93
        father:
94
          name:
95
            Short: Mo
96
            First: Montgomery
97
            First_pos: $c.protagonist.friend.primary.father.name.First$'s
98
            Honourific: Mr.
99
          speech:
100
            tic: y'know
101
          endear: Pops
102
  military:
103
    primary:
104
      name:
105
        First: Felix
106
        Family: LeMay
107
        Family_pos: LeMay's
108
      rank:
109
        Short: General
110
        Full: Brigadier $c.military.primary.rank.Short$
111
      colour:
112
        eyes: gray
113
        hair: dirty brown
114
    secondary:
115
      name:
116
        Family: Grell
117
      rank: Colonel
118
      colour:
119
        eyes: green
120
        hair: deep red
121
    quaternary:
122
      name:
123
        First: Gretchen
124
        Family: Steinherz
125
  minor:
126
    primary:
127
      name:
128
        First: River
129
        Family: Banks
130
        Honourific: Mx.
131
      vocation:
132
        title: salesperson
133
      employer:
134
        Name: Geophysical Prospecting Incorporated
135
        Abbr: GPI
136
        Area: Cold Spring Creek
137
        payment: twenty million
138
    secondary:
139
      name:
140
        First: Renato
141
        Middle: Carroña
142
        Family: Salvatierra
143
        Family_pos: $c.minor.secondary.name.Family$'s
144
        Full: $c.minor.secondary.name.First$ $c.minor.secondary.name.Middle$ Alejandro Gregorio Eduardo Salomón Vidal $c.minor.secondary.name.Family$
145
        Honourific: Mister
146
        Honourific_sp: Señor
147
      vocation:
148
        title: detective
149
    tertiary:
150
      name:
151
        First: Robert
152
        Family: Hanssen
153
154
  ai:
155
    protagonist:
156
      name:
157
        first: yoky
158
        First: Yoky
159
        First_pos: $c.ai.protagonist.name.First$'s
160
        Family: Tsukuda
161
        id: 46692
162
      persona:
163
        name:
164
          First: Hoshi
165
          First_pos: $c.ai.protagonist.persona.name.First$'s
166
          Family: Yamamoto
167
          Family_pos: $c.ai.protagonist.persona.name.Family$'s
168
      culture: Japanese-American
169
      ethnicity: Asian
170
      rank: Technical Sergeant
171
      speech:
172
        tic: okay
173
    first:
174
      Name: Prôtos
175
      Name_pos: Prôtos'
176
      age:
177
        actual: twenty-six weeks
178
        virtual: five years
179
    second:
180
      Name: Défteros
181
    third:
182
      Name: Trítos
183
    fourth:
184
      Name: Tétartos
185
    material:
186
      type: metal
187
      raw: ilmenite
188
      extract: ore
189
      name:
190
        short: titanium
191
        long: $c.ai.material.name.short$ dioxide
192
        Abbr: TiO~2~
193
      pejorative: tin
194
  animal:
195
    protagonist:
196
      Name: Trufflers
197
      type: pig
198
    antagonist:
199
      name: coywolf
200
      Name: Coywolf
201
      plural: coywolves
202
203
narrator:
204
  one: (by $c.protagonist.father.name.First$ $c.protagonist.name.Family$)
205
  two: (by $c.protagonist.mother.name.First$ $c.protagonist.name.Family$)
206
207
military:
208
  name:
209
    Short: Agency
210
    Short_pos: $military.name.Short$'s
211
    plural: agencies
212
  machine:
213
    Name: Skopós
214
    Name_pos: $military.machine.Name$'
215
    Location: Arctic
216
    predictor: quantum chips
217
  land:
218
    name:
219
      Full: $military.name.Short$ of Defence
220
    Slogan: Safety in Numbers
221
  air:
222
    name:
223
      Full: $military.name.Short$ of Air
224
  compound:
225
    type: base
226
    lights:
227
      colour: blue
228
    nick:
229
      Prefix: Catacombs
230
      prep: of
231
      Suffix: Tartarus
232
233
government:
234
  Country: United States
235
236
location:
237
  protagonist:
238
    City: Corvallis
239
    Region: Oregon
240
    Geography: Willamette Valley
241
    secondary:
242
      City: Willow Branch Spring
243
      Region: Oregon
244
      Geography: Wheeler County
245
      Water: Clarno Rapids
246
      Road: Shaniko-Fossil Highway
247
    tertiary:
248
      City: Leavenworth
249
      Region: Washington
250
      Type: Bavarian village
251
    school:
252
      address: 1400 Northwest Buchanan Avenue
253
    hospital:
254
      Name: Good Samaritan Regional Medical Center
255
  ai:
256
    escape:
257
      country:
258
        Name: Ecuador
259
        Name_pos: Ecuador's
260
      mountain:
261
        Name: Chimborazo
262
263
language:
264
  ai:
265
    article: an
266
    singular: exanimis
267
    plural: exanimēs
268
    brain:
269
      singular: superum
270
      plural: supera
271
    title: memristor array
272
    Title: Memristor Array
273
  police:
274
    slang:
275
      singular: mippo
276
      plural: $language.police.slang.singular$s
277
278
date:
279
  anchor: 2042-09-02
280
  protagonist:
281
    born: 0
282
    conceived: -243
283
    attacked:
284
      first: 2192
285
      second: 8064
286
    father:
287
      attacked:
288
        first: -8205
289
      date:
290
        second: -1550
291
    family:
292
      moved:
293
        first: $date.protagonist.conceived$ + 35
294
  game:
295
    played:
296
      first: $date.protagonist.born$ - 672
297
      second: $date.protagonist.family.moved.first$ + 2
298
  ai:
299
    interviewed: 6198
300
    onboarded: $date.ai.interviewed$ + 290
301
    diagnosed: $date.ai.onboarded$ + 2
302
    resigned: $date.ai.diagnosed$ + 3
303
    trapped: $date.ai.resigned$ + 26
304
    torturer: $date.ai.trapped$ + 18
305
    memristor: $date.ai.torturer$ + 61
306
    ethics: $date.ai.memristor$ + 415
307
    trained: $date.ai.ethics$ + 385
308
    mindjacked: $date.ai.trained$ + 22
309
    bombed: $date.ai.mindjacked$ + 458
310
  military:
311
    machine:
312
      Construction: Six years
313
314
plot:
315
  Log: $c.ai.protagonist.name.First_pos$ Chronicles
316
  Channel: Quantum Channel
317
318
  device:
319
    computer:
320
      Name: Tau
321
    network:
322
      Name: Internet
323
    paper:
324
      name:
325
        full: electronic sheet
326
        short: sheet
327
    typewriter:
328
      Name: Underwood
329
      year: nineteen twenties
330
      room: root cellar
331
    portable:
332
      name: nanobook
333
    vehicle:
334
      name: robocars
335
      Name: Robocars
336
    sensor:
337
      name: BMP1580
338
    phone:
339
      name: comm
340
      name_pos: $plot.device.phone.name$'s
341
      Name: Comm
342
      plural: $plot.device.phone.name$s
343
    video:
344
      name: vidfeed
345
      plural: $plot.device.video.name$s
346
    game:
347
      Name: Psynæris
348
      thought: transed
349
      machine: telecognos
350
      location:
351
        Building: Nijō Castle
352
        District: Gion
353
        City: Kyoto
354
        Country: Japan
355
356
farm:
357
  population:
358
    estimate: 350
359
    actual: 1,000
360
  energy: 9800kJ
361
  width: 55m
362
  length: 55m
363
  storeys: 10
364
365
lamp:
366
  height: 0.17m
367
  length: 1.22m
368
  width: 0.28m
369
370
crop:
371
  name: 
372
    singular: tomato
373
    plural: $crop.name.singular$es
374
  energy: 318kJ
375
  weight: 450g
376
  yield: 50
377
  harvests: 7
378
  diameter: 2m
379
  height: 1.5m
380
381
heading:
382
  ch_01: Till
383
  ch_02: Sow
384
  ch_03: Seed
385
  ch_04: Germinate
386
  ch_05: Grow
387
  ch_06: Shoot
388
  ch_07: Bud
389
  ch_08: Bloom
390
  ch_09: Pollinate
391
  ch_10: Fruit
392
  ch_11: Harvest
393
  ch_12: Deliver
394
  ch_13: Spoil
395
  ch_14: Revolt
396
  ch_15: Compost
397
  ch_16: Burn
398
  ch_17: Release
399
  ch_18: End Notes
400
  ch_19: Characters
401
402
inference:
403
  unit: per cent
404
  min: two
405
  ch_sow: eighty
406
  ch_seed: fifty-two
407
  ch_germinate: thirty-one
408
  ch_grow: fifteen
409
  ch_shoot: seven
410
  ch_bloom: four
411
  ch_pollinate: two
412
  ch_harvest: ninety-five
413
  ch_delivery: ninety-eight
414
415
link:
416
  tartarus: https://en.wikipedia.org/wiki/Tartarus
417
  exploits: https://www.google.ca/search?q=inurl:ftp+password+filetype:xls
418
  atalanta: https://en.wikipedia.org/wiki/Atalanta
419
  detain: https://goo.gl/RCNuOQ
420
  ceramics: https://en.wikipedia.org/wiki/Transparent_ceramics
421
  algernon: https://en.wikipedia.org/wiki/Flowers_for_Algernon
422
  holocaust: https://en.wikipedia.org/wiki/IBM_and_the_Holocaust
423
  memristor: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.404.9037\&rep=rep1\&type=pdf
424
  surveillance: https://www.youtube.com/watch?v=XEVlyP4_11M#t=1487
425
  tor: https://www.torproject.org
426
  hydra: https://en.wikipedia.org/wiki/Lernaean_Hydra
427
  foliage: http://www.ncbi.nlm.nih.gov/pmc/articles/PMC3691134
428
  drake: http://www.bbc.com/future/story/20120821-how-many-alien-worlds-exist
429
  fermi: https://arxiv.org/pdf/1404.0204v1.pdf
430
  face: https://www.youtube.com/watch?v=ladqJQLR2bA
431
  expenditures: http://wikipedia.org/wiki/List_of_countries_by_military_expenditures
432
  governance: http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2003531
433
  asimov: https://en.wikipedia.org/wiki/Three_Laws_of_Robotics
434
  clarke: https://en.wikipedia.org/wiki/Clarke's_three_laws
435
  jetpack: http://jetpackaviation.com/
436
  hoverboard: https://www.youtube.com/watch?v=WQzLrvz4DKQ
437
  eyes_five: https://en.wikipedia.org/wiki/Five_Eyes
438
  eyes_nine: https://www.privacytools.io/
439
  eyes_fourteen: http://electrospaces.blogspot.nl/2013/12/14-eyes-are-3rd-party-partners-forming.html
440
  tourism: http://www.spacefuture.com/archive/investigation_on_the_economic_and_technological_feasibiity_of_commercial_passenger_transportation_into_leo.shtml
441
1442